<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Interactive BOM for KiCAD</title>
  <style type="text/css">
:root {
  --pcb-edge-color: black;
  --pad-color: #878787;
  --pad-hole-color: #CCCCCC;
  --pad-color-highlight: #D04040;
  --pin1-outline-color: #ffb629;
  --pin1-outline-color-highlight: #b4ff03;
  --silkscreen-edge-color: #aa4;
  --silkscreen-polygon-color: #4aa;
  --silkscreen-text-color: #4aa;
  --fabrication-edge-color: #907651;
  --fabrication-polygon-color: #907651;
  --fabrication-text-color: #a27c24;
  --track-color: #def5f1;
  --track-color-highlight: #D04040;
  --zone-color: #def5f1;
  --zone-color-highlight: #d0404080;
}

html, body {
  margin: 0px;
  height: 100%;
  font-family: Verdana, sans-serif;
}

.dark.topmostdiv {
  --pcb-edge-color: #eee;
  --pad-color: #808080;
  --pin1-outline-color: #ffa800;
  --pin1-outline-color-highlight: #ccff00;
  --track-color: #42524f;
  --zone-color: #42524f;
  background-color: #252c30;
  color: #eee;
}

button {
  background-color: #eee;
  border: 1px solid #888;
  color: black;
  height: 44px;
  width: 44px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 14px;
  font-weight: bolder;
}

.dark button {
  /* This will be inverted */
  background-color: #c3b7b5;
}

button.depressed {
  background-color: #0a0;
  color: white;
}

.dark button.depressed {
  /* This will be inverted */
  background-color: #b3b;
}

button:focus {
  outline: 0;
}

button#tb-btn {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8.47 8.47'%3E%3Crect transform='translate(0 -288.53)' ry='1.17' y='288.8' x='.27' height='7.94' width='7.94' fill='%23f9f9f9'/%3E%3Cg transform='translate(0 -288.53)'%3E%3Crect width='7.94' height='7.94' x='.27' y='288.8' ry='1.17' fill='none' stroke='%23000' stroke-width='.4' stroke-linejoin='round'/%3E%3Cpath d='M1.32 290.12h5.82M1.32 291.45h5.82' fill='none' stroke='%23000' stroke-width='.4'/%3E%3Cpath d='M4.37 292.5v4.23M.26 292.63H8.2' fill='none' stroke='%23000' stroke-width='.3'/%3E%3Ctext font-weight='700' font-size='3.17' font-family='sans-serif'%3E%3Ctspan x='1.35' y='295.73'%3EF%3C/tspan%3E%3Ctspan x='5.03' y='295.68'%3EB%3C/tspan%3E%3C/text%3E%3C/g%3E%3C/svg%3E%0A");
}

button#lr-btn {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8.47 8.47'%3E%3Crect transform='translate(0 -288.53)' ry='1.17' y='288.8' x='.27' height='7.94' width='7.94' fill='%23f9f9f9'/%3E%3Cg transform='translate(0 -288.53)'%3E%3Crect width='7.94' height='7.94' x='.27' y='288.8' ry='1.17' fill='none' stroke='%23000' stroke-width='.4' stroke-linejoin='round'/%3E%3Cpath d='M1.06 290.12H3.7m-2.64 1.33H3.7m-2.64 1.32H3.7m-2.64 1.3H3.7m-2.64 1.33H3.7' fill='none' stroke='%23000' stroke-width='.4'/%3E%3Cpath d='M4.37 288.8v7.94m0-4.11h3.96' fill='none' stroke='%23000' stroke-width='.3'/%3E%3Ctext font-weight='700' font-size='3.17' font-family='sans-serif'%3E%3Ctspan x='5.11' y='291.96'%3EF%3C/tspan%3E%3Ctspan x='5.03' y='295.68'%3EB%3C/tspan%3E%3C/text%3E%3C/g%3E%3C/svg%3E%0A");
}

button#bom-btn {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8.47 8.47'%3E%3Crect transform='translate(0 -288.53)' ry='1.17' y='288.8' x='.27' height='7.94' width='7.94' fill='%23f9f9f9'/%3E%3Cg transform='translate(0 -288.53)' fill='none' stroke='%23000' stroke-width='.4'%3E%3Crect width='7.94' height='7.94' x='.27' y='288.8' ry='1.17' stroke-linejoin='round'/%3E%3Cpath d='M1.59 290.12h5.29M1.59 291.45h5.33M1.59 292.75h5.33M1.59 294.09h5.33M1.59 295.41h5.33'/%3E%3C/g%3E%3C/svg%3E");
}

button#bom-grouped-btn {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cg stroke='%23000' stroke-linejoin='round' class='layer'%3E%3Crect width='29' height='29' x='1.5' y='1.5' stroke-width='2' fill='%23fff' rx='5' ry='5'/%3E%3Cpath stroke-linecap='square' stroke-width='2' d='M6 10h4m4 0h5m4 0h3M6.1 22h3m3.9 0h5m4 0h4m-16-8h4m4 0h4'/%3E%3Cpath stroke-linecap='null' d='M5 17.5h22M5 26.6h22M5 5.5h22'/%3E%3C/g%3E%3C/svg%3E");
}

button#bom-ungrouped-btn {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cg stroke='%23000' stroke-linejoin='round' class='layer'%3E%3Crect width='29' height='29' x='1.5' y='1.5' stroke-width='2' fill='%23fff' rx='5' ry='5'/%3E%3Cpath stroke-linecap='square' stroke-width='2' d='M6 10h4m-4 8h3m-3 8h4'/%3E%3Cpath stroke-linecap='null' d='M5 13.5h22m-22 8h22M5 5.5h22'/%3E%3C/g%3E%3C/svg%3E");
}

button#bom-netlist-btn {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cg fill='none' stroke='%23000' class='layer'%3E%3Crect width='29' height='29' x='1.5' y='1.5' stroke-width='2' fill='%23fff' rx='5' ry='5'/%3E%3Cpath stroke-width='2' d='M6 26l6-6v-8m13.8-6.3l-6 6v8'/%3E%3Ccircle cx='11.8' cy='9.5' r='2.8' stroke-width='2'/%3E%3Ccircle cx='19.8' cy='22.8' r='2.8' stroke-width='2'/%3E%3C/g%3E%3C/svg%3E");
}

button#copy {
  background-image: url("data:image/svg+xml,%3Csvg height='48' viewBox='0 0 48 48' width='48' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0h48v48h-48z' fill='none'/%3E%3Cpath d='M32 2h-24c-2.21 0-4 1.79-4 4v28h4v-28h24v-4zm6 8h-22c-2.21 0-4 1.79-4 4v28c0 2.21 1.79 4 4 4h22c2.21 0 4-1.79 4-4v-28c0-2.21-1.79-4-4-4zm0 32h-22v-28h22v28z'/%3E%3C/svg%3E");
  background-position: 6px 6px;
  background-repeat: no-repeat;
  background-size: 26px 26px;
  border-radius: 6px;
  height: 40px;
  width: 40px;
  margin: 10px 5px;
}

button#copy:active {
    box-shadow: inset 0px 0px 5px #6c6c6c;
}

textarea.clipboard-temp {
  position: fixed;
  top: 0;
  left: 0;
  width: 2em;
  height: 2em;
  padding: 0;
  border: None;
  outline: None;
  box-shadow: None;
  background: transparent;
}

.left-most-button {
  border-right: 0;
  border-top-left-radius: 6px;
  border-bottom-left-radius: 6px;
}

.middle-button {
  border-right: 0;
}

.right-most-button {
  border-top-right-radius: 6px;
  border-bottom-right-radius: 6px;
}

.button-container {
  font-size: 0;
  margin: 10px 10px 10px 0px;
}

.dark .button-container {
  filter: invert(1);
}

.button-container button {
  background-size: 32px 32px;
  background-position: 5px 5px;
  background-repeat: no-repeat;
}

@media print {
  .hideonprint {
    display: none;
  }
}

canvas {
  cursor: crosshair;
}

canvas:active {
  cursor: grabbing;
}

.fileinfo {
  width: 100%;
  max-width: 1000px;
  border: none;
  padding: 5px;
}

.fileinfo .title {
  font-size: 20pt;
  font-weight: bold;
}

.fileinfo td {
  overflow: hidden;
  white-space: nowrap;
  max-width: 1px;
  width: 50%;
  text-overflow: ellipsis;
}

.bom {
  border-collapse: collapse;
  font-family: Consolas, "DejaVu Sans Mono", Monaco, monospace;
  font-size: 10pt;
  table-layout: fixed;
  width: 100%;
  margin-top: 1px;
}

.bom th, .bom td {
  border: 1px solid black;
  padding: 5px;
  word-wrap: break-word;
  text-align: center;
  position: relative;
}

.dark .bom th, .dark .bom td {
  border: 1px solid #777;
}

.bom th {
  background-color: #CCCCCC;
  background-clip: padding-box;
}

.dark .bom th {
  background-color: #3b4749;
}

.bom tr.highlighted:nth-child(n) {
  background-color: #cfc;
}

.dark .bom tr.highlighted:nth-child(n) {
  background-color: #226022;
}

.bom tr:nth-child(even) {
  background-color: #f2f2f2;
}

.dark .bom tr:nth-child(even) {
  background-color: #313b40;
}

.bom tr.checked {
  color: #aaa;
}

.dark .bom tr.checked {
  color: #666;
}

.bom tr {
  transition: background-color 0.2s;
}

.bom .numCol {
  width: 25px;
}

.bom .Description {
  width: 10%;
}

.bom .Part {
  width: 10%;
}

.bom .Value {
  width: 15%;
}

.bom .Quantity {
  width: 65px;
}

.bom th .sortmark {
  position: absolute;
  right: 1px;
  top: 1px;
  margin-top: -5px;
  border-width: 5px;
  border-style: solid;
  border-color: transparent transparent #221 transparent;
  transform-origin: 50% 85%;
  transition: opacity 0.2s, transform 0.4s;
}

.dark .bom th .sortmark {
  filter: invert(1);
}

.bom th .sortmark.none {
  opacity: 0;
}

.bom th .sortmark.desc {
  transform: rotate(180deg);
}

.bom th:hover .sortmark.none {
  opacity: 0.5;
}

.bom .bom-checkbox {
  width: 30px;
  position: relative;
  user-select: none;
  -moz-user-select: none;
}

.bom .bom-checkbox:before {
  content: "";
  position: absolute;
  border-width: 15px;
  border-style: solid;
  border-color: #51829f transparent transparent transparent;
  visibility: hidden;
  top: -15px;
}

.bom .bom-checkbox:after {
  content: "Double click to set/unset all";
  position: absolute;
  color: white;
  top: -35px;
  left: -26px;
  background: #51829f;
  padding: 5px 15px;
  border-radius: 8px;
  white-space: nowrap;
  visibility: hidden;
}

.bom .bom-checkbox:hover:before, .bom .bom-checkbox:hover:after {
  visibility: visible;
  transition: visibility 0.2s linear 1s;
}

.split {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  overflow-y: auto;
  overflow-x: hidden;
  background-color: inherit;
}

.split.split-horizontal, .gutter.gutter-horizontal {
  height: 100%;
  float: left;
}

.gutter {
  background-color: #ddd;
  background-repeat: no-repeat;
  background-position: 50%;
  transition: background-color 0.3s;
}

.dark .gutter {
  background-color: #777;
}

.gutter.gutter-horizontal {
  background-image: url('');
  cursor: ew-resize;
  width: 5px;
}

.gutter.gutter-vertical {
  background-image: url('');
  cursor: ns-resize;
  height: 5px;
}

.searchbox {
  float: left;
  height: 40px;
  margin: 10px 5px;
  padding: 12px 32px;
  font-family: Consolas, "DejaVu Sans Mono", Monaco, monospace;
  font-size: 18px;
  box-sizing: border-box;
  border: 1px solid #888;
  border-radius: 6px;
  outline: none;
  background-color: #eee;
  transition: background-color 0.2s, border 0.2s;
  background-image: url('');
  background-position: 10px 10px;
  background-repeat: no-repeat;
}

.dark .searchbox {
  background-color: #111;
  color: #eee;
}

.searchbox::placeholder {
  color: #ccc;
}

.dark .searchbox::placeholder {
  color: #666;
}

.filter {
  width: calc(60% - 64px);
}

.reflookup {
  width: calc(40% - 10px);
}

input[type=text]:focus {
  background-color: white;
  border: 1px solid #333;
}

.dark input[type=text]:focus {
  background-color: #333;
  border: 1px solid #ccc;
}

mark.highlight {
  background-color: #5050ff;
  color: #fff;
  padding: 2px;
  border-radius: 6px;
}

.dark mark.highlight {
  background-color: #76a6da;
  color: #111;
}

.menubtn {
  background-color: white;
  border: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='36' viewBox='0 0 20 20'%3E%3Cpath fill='none' d='M0 0h20v20H0V0z'/%3E%3Cpath d='M15.95 10.78c.03-.25.05-.51.05-.78s-.02-.53-.06-.78l1.69-1.32c.15-.12.19-.34.1-.51l-1.6-2.77c-.1-.18-.31-.24-.49-.18l-1.99.8c-.42-.32-.86-.58-1.35-.78L12 2.34c-.03-.2-.2-.34-.4-.34H8.4c-.2 0-.36.14-.39.34l-.3 2.12c-.49.2-.94.47-1.35.78l-1.99-.8c-.18-.07-.39 0-.49.18l-1.6 2.77c-.1.18-.06.39.1.51l1.69 1.32c-.04.25-.07.52-.07.78s.02.53.06.78L2.37 12.1c-.15.12-.19.34-.1.51l1.6 2.77c.1.18.31.24.49.18l1.99-.8c.42.32.86.58 1.35.78l.3 2.12c.04.2.2.34.4.34h3.2c.2 0 .37-.14.39-.34l.3-2.12c.49-.2.94-.47 1.35-.78l1.99.8c.18.07.39 0 .49-.18l1.6-2.77c.1-.18.06-.39-.1-.51l-1.67-1.32zM10 13c-1.65 0-3-1.35-3-3s1.35-3 3-3 3 1.35 3 3-1.35 3-3 3z'/%3E%3C/svg%3E%0A");
  background-position: center;
  background-repeat: no-repeat;
}

.statsbtn {
  background-color: white;
  border: none;
  background-image: url("data:image/svg+xml,%3Csvg width='36' height='36' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 6h28v24H4V6zm0 8h28v8H4m9-16v24h10V5.8' fill='none' stroke='%23000' stroke-width='2'/%3E%3C/svg%3E");
  background-position: center;
  background-repeat: no-repeat;
}

.iobtn {
  background-color: white;
  border: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='36'%3E%3Cpath fill='none' stroke='%23000' stroke-width='2' d='M3 33v-7l6.8-7h16.5l6.7 7v7H3zM3.2 26H33M21 9l5-5.9 5 6h-2.5V15h-5V9H21zm-4.9 0l-5 6-5-6h2.5V3h5v6h2.5z'/%3E%3Cpath fill='none' stroke='%23000' d='M6.1 29.5H10'/%3E%3C/svg%3E");
  background-position: center;
  background-repeat: no-repeat;
}

.dark .statsbtn, .dark .savebtn, .dark .menubtn, .dark .iobtn {
  filter: invert(1);
}

.flexbox {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
}

.savebtn {
  background-color: #d6d6d6;
  width: auto;
  height: 30px;
  flex-grow: 1;
  margin: 5px;
  border-radius: 4px;
}

.savebtn:active {
  background-color: #0a0;
  color: white;
}

.dark .savebtn:active {
  /* This will be inverted */
  background-color: #b3b;
}

.stats {
  border-collapse: collapse;
  font-size: 12pt;
  table-layout: fixed;
  width: 100%;
  min-width: 450px;
}

.dark .stats td {
  border: 1px solid #bbb;
}

.stats td {
  border: 1px solid black;
  padding: 5px;
  word-wrap: break-word;
  text-align: center;
  position: relative;
}

#checkbox-stats div {
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

#checkbox-stats .bar {
  background-color: rgba(28, 251, 0, 0.6);
}

.menu {
  position: relative;
  display: inline-block;
  margin: 10px 10px 10px 0px;
}

.menu-content {
  display: none;
  position: absolute;
  background-color: white;
  right: 0;
  min-width: 300px;
  box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
  z-index: 100;
  padding: 8px;
}

.dark .menu-content {
  background-color: #111;
}

.menu:hover .menu-content {
  display: block;
}

.menu:hover .menubtn, .menu:hover .iobtn, .menu:hover .statsbtn {
  background-color: #eee;
}

.menu-label {
  display: inline-block;
  padding: 8px;
  border: 1px solid #ccc;
  border-top: 0;
  width: calc(100% - 18px);
}

.menu-label-top {
  border-top: 1px solid #ccc;
}

.menu-textbox {
  float: left;
  height: 24px;
  margin: 10px 5px;
  padding: 5px 5px;
  font-family: Consolas, "DejaVu Sans Mono", Monaco, monospace;
  font-size: 14px;
  box-sizing: border-box;
  border: 1px solid #888;
  border-radius: 4px;
  outline: none;
  background-color: #eee;
  transition: background-color 0.2s, border 0.2s;
  width: calc(100% - 10px);
}

.menu-textbox.invalid, .dark .menu-textbox.invalid {
  color: red;
}

.dark .menu-textbox {
  background-color: #222;
  color: #eee;
}

.radio-container {
  margin: 4px;
}

.topmostdiv {
  width: 100%;
  height: 100%;
  background-color: white;
  transition: background-color 0.3s;
}

#top {
  height: 78px;
  border-bottom: 2px solid black;
}

.dark #top {
  border-bottom: 2px solid #ccc;
}

#dbg {
  display: block;
}

::-webkit-scrollbar {
  width: 8px;
}

::-webkit-scrollbar-track {
  background: #aaa;
}

::-webkit-scrollbar-thumb {
  background: #666;
  border-radius: 3px;
}

::-webkit-scrollbar-thumb:hover {
  background: #555;
}

.slider {
  -webkit-appearance: none;
  width: 100%;
  margin: 3px 0;
  padding: 0;
  outline: none;
  opacity: 0.7;
  -webkit-transition: .2s;
  transition: opacity .2s;
  border-radius: 3px;
}

.slider:hover {
  opacity: 1;
}

.slider:focus {
  outline: none;
}

.slider::-webkit-slider-runnable-track {
  -webkit-appearance: none;
  width: 100%;
  height: 8px;
  background: #d3d3d3;
  border-radius: 3px;
  border: none;
}

.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: #0a0;
  cursor: pointer;
  margin-top: -4px;
}

.dark .slider::-webkit-slider-thumb {
  background: #3d3;
}

.slider::-moz-range-thumb {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: #0a0;
  cursor: pointer;
}

.slider::-moz-range-track {
  height: 8px;
  background: #d3d3d3;
  border-radius: 3px;
}

.dark .slider::-moz-range-thumb {
  background: #3d3;
}

.slider::-ms-track {
  width: 100%;
  height: 8px;
  border-width: 3px 0;
  background: transparent;
  border-color: transparent;
  color: transparent;
  transition: opacity .2s;
}

.slider::-ms-fill-lower {
  background: #d3d3d3;
  border: none;
  border-radius: 3px;
}

.slider::-ms-fill-upper {
  background: #d3d3d3;
  border: none;
  border-radius: 3px;
}

.slider::-ms-thumb {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: #0a0;
  cursor: pointer;
  margin: 0;
}

.shameless-plug {
  font-size: 0.8em;
  text-align: center;
  display: block;
}

a {
  color: #0278a4;
}

.dark a {
  color: #00b9fd;
}

#frontcanvas, #backcanvas {
    touch-action: none;
}

 
  </style>
  <script type="text/javascript" >
///////////////////////////////////////////////
/*
  Split.js - v1.3.5
  MIT License
  https://github.com/nathancahill/Split.js
*/
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Split=t()}(this,function(){"use strict";var e=window,t=e.document,n="addEventListener",i="removeEventListener",r="getBoundingClientRect",s=function(){return!1},o=e.attachEvent&&!e[n],a=["","-webkit-","-moz-","-o-"].filter(function(e){var n=t.createElement("div");return n.style.cssText="width:"+e+"calc(9px)",!!n.style.length}).shift()+"calc",l=function(e){return"string"==typeof e||e instanceof String?t.querySelector(e):e};return function(u,c){function z(e,t,n){var i=A(y,t,n);Object.keys(i).forEach(function(t){return e.style[t]=i[t]})}function h(e,t){var n=B(y,t);Object.keys(n).forEach(function(t){return e.style[t]=n[t]})}function f(e){var t=E[this.a],n=E[this.b],i=t.size+n.size;t.size=e/this.size*i,n.size=i-e/this.size*i,z(t.element,t.size,this.aGutterSize),z(n.element,n.size,this.bGutterSize)}function m(e){var t;this.dragging&&((t="touches"in e?e.touches[0][b]-this.start:e[b]-this.start)<=E[this.a].minSize+M+this.aGutterSize?t=E[this.a].minSize+this.aGutterSize:t>=this.size-(E[this.b].minSize+M+this.bGutterSize)&&(t=this.size-(E[this.b].minSize+this.bGutterSize)),f.call(this,t),c.onDrag&&c.onDrag())}function g(){var e=E[this.a].element,t=E[this.b].element;this.size=e[r]()[y]+t[r]()[y]+this.aGutterSize+this.bGutterSize,this.start=e[r]()[G]}function d(){var t=this,n=E[t.a].element,r=E[t.b].element;t.dragging&&c.onDragEnd&&c.onDragEnd(),t.dragging=!1,e[i]("mouseup",t.stop),e[i]("touchend",t.stop),e[i]("touchcancel",t.stop),t.parent[i]("mousemove",t.move),t.parent[i]("touchmove",t.move),delete t.stop,delete t.move,n[i]("selectstart",s),n[i]("dragstart",s),r[i]("selectstart",s),r[i]("dragstart",s),n.style.userSelect="",n.style.webkitUserSelect="",n.style.MozUserSelect="",n.style.pointerEvents="",r.style.userSelect="",r.style.webkitUserSelect="",r.style.MozUserSelect="",r.style.pointerEvents="",t.gutter.style.cursor="",t.parent.style.cursor=""}function S(t){var i=this,r=E[i.a].element,o=E[i.b].element;!i.dragging&&c.onDragStart&&c.onDragStart(),t.preventDefault(),i.dragging=!0,i.move=m.bind(i),i.stop=d.bind(i),e[n]("mouseup",i.stop),e[n]("touchend",i.stop),e[n]("touchcancel",i.stop),i.parent[n]("mousemove",i.move),i.parent[n]("touchmove",i.move),r[n]("selectstart",s),r[n]("dragstart",s),o[n]("selectstart",s),o[n]("dragstart",s),r.style.userSelect="none",r.style.webkitUserSelect="none",r.style.MozUserSelect="none",r.style.pointerEvents="none",o.style.userSelect="none",o.style.webkitUserSelect="none",o.style.MozUserSelect="none",o.style.pointerEvents="none",i.gutter.style.cursor=j,i.parent.style.cursor=j,g.call(i)}function v(e){e.forEach(function(t,n){if(n>0){var i=F[n-1],r=E[i.a],s=E[i.b];r.size=e[n-1],s.size=t,z(r.element,r.size,i.aGutterSize),z(s.element,s.size,i.bGutterSize)}})}function p(){F.forEach(function(e){e.parent.removeChild(e.gutter),E[e.a].element.style[y]="",E[e.b].element.style[y]=""})}void 0===c&&(c={});var y,b,G,E,w=l(u[0]).parentNode,D=e.getComputedStyle(w).flexDirection,U=c.sizes||u.map(function(){return 100/u.length}),k=void 0!==c.minSize?c.minSize:100,x=Array.isArray(k)?k:u.map(function(){return k}),L=void 0!==c.gutterSize?c.gutterSize:10,M=void 0!==c.snapOffset?c.snapOffset:30,O=c.direction||"horizontal",j=c.cursor||("horizontal"===O?"ew-resize":"ns-resize"),C=c.gutter||function(e,n){var i=t.createElement("div");return i.className="gutter gutter-"+n,i},A=c.elementStyle||function(e,t,n){var i={};return"string"==typeof t||t instanceof String?i[e]=t:i[e]=o?t+"%":a+"("+t+"% - "+n+"px)",i},B=c.gutterStyle||function(e,t){return n={},n[e]=t+"px",n;var n};"horizontal"===O?(y="width","clientWidth",b="clientX",G="left","paddingLeft"):"vertical"===O&&(y="height","clientHeight",b="clientY",G="top","paddingTop");var F=[];return E=u.map(function(e,t){var i,s={element:l(e),size:U[t],minSize:x[t]};if(t>0&&(i={a:t-1,b:t,dragging:!1,isFirst:1===t,isLast:t===u.length-1,direction:O,parent:w},i.aGutterSize=L,i.bGutterSize=L,i.isFirst&&(i.aGutterSize=L/2),i.isLast&&(i.bGutterSize=L/2),"row-reverse"===D||"column-reverse"===D)){var a=i.a;i.a=i.b,i.b=a}if(!o&&t>0){var c=C(t,O);h(c,L),c[n]("mousedown",S.bind(i)),c[n]("touchstart",S.bind(i)),w.insertBefore(c,s.element),i.gutter=c}0===t||t===u.length-1?z(s.element,s.size,L/2):z(s.element,s.size,L);var f=s.element[r]()[y];return f<s.minSize&&(s.minSize=f),t>0&&F.push(i),s}),o?{setSizes:v,destroy:p}:{setSizes:v,getSizes:function(){return E.map(function(e){return e.size})},collapse:function(e){if(e===F.length){var t=F[e-1];g.call(t),o||f.call(t,t.size-t.bGutterSize)}else{var n=F[e];g.call(n),o||f.call(n,n.aGutterSize)}},destroy:p}}});

///////////////////////////////////////////////

///////////////////////////////////////////////
// Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
// This work is free. You can redistribute it and/or modify it
// under the terms of the WTFPL, Version 2
// For more information see LICENSE.txt or http://www.wtfpl.net/
//
// For more information, the home page:
// http://pieroxy.net/blog/pages/lz-string/testing.html
//
// LZ-based compression algorithm, version 1.4.4
var LZString=function(){var o=String.fromCharCode,i={};var n={decompressFromBase64:function(o){return null==o?"":""==o?null:n._decompress(o.length,32,function(n){return function(o,n){if(!i[o]){i[o]={};for(var t=0;t<o.length;t++)i[o][o.charAt(t)]=t}return i[o][n]}("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",o.charAt(n))})},_decompress:function(i,n,t){var r,e,a,s,p,u,l,f=[],c=4,d=4,h=3,v="",g=[],m={val:t(0),position:n,index:1};for(r=0;r<3;r+=1)f[r]=r;for(a=0,p=Math.pow(2,2),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;switch(a){case 0:for(a=0,p=Math.pow(2,8),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;l=o(a);break;case 1:for(a=0,p=Math.pow(2,16),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;l=o(a);break;case 2:return""}for(f[3]=l,e=l,g.push(l);;){if(m.index>i)return"";for(a=0,p=Math.pow(2,h),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;switch(l=a){case 0:for(a=0,p=Math.pow(2,8),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;f[d++]=o(a),l=d-1,c--;break;case 1:for(a=0,p=Math.pow(2,16),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;f[d++]=o(a),l=d-1,c--;break;case 2:return g.join("")}if(0==c&&(c=Math.pow(2,h),h++),f[l])v=f[l];else{if(l!==d)return null;v=e+e.charAt(0)}g.push(v),f[d++]=e+v.charAt(0),e=v,0==--c&&(c=Math.pow(2,h),h++)}}};return n}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module?module.exports=LZString:"undefined"!=typeof angular&&null!=angular&&angular.module("LZString",[]).factory("LZString",function(){return LZString});
///////////////////////////////////////////////

///////////////////////////////////////////////
/*!
 * PEP v0.4.3 | https://github.com/jquery/PEP
 * Copyright jQuery Foundation and other contributors | http://jquery.org/license
 */
! function (a, b) {
  "object" == typeof exports && "undefined" != typeof module ? module.exports = b() : "function" == typeof define && define.amd ? define(b) : a.PointerEventsPolyfill = b()
}(this, function () {
  "use strict";

  function a(a, b) {
    b = b || Object.create(null);
    var c = document.createEvent("Event");
    c.initEvent(a, b.bubbles || !1, b.cancelable || !1);
    for (var d, e = 2; e < m.length; e++) d = m[e], c[d] = b[d] || n[e];
    c.buttons = b.buttons || 0;
    var f = 0;
    return f = b.pressure && c.buttons ? b.pressure : c.buttons ? .5 : 0, c.x = c.clientX, c.y = c.clientY, c.pointerId = b.pointerId || 0, c.width = b.width || 0, c.height = b.height || 0, c.pressure = f, c.tiltX = b.tiltX || 0, c.tiltY = b.tiltY || 0, c.twist = b.twist || 0, c.tangentialPressure = b.tangentialPressure || 0, c.pointerType = b.pointerType || "", c.hwTimestamp = b.hwTimestamp || 0, c.isPrimary = b.isPrimary || !1, c
  }

  function b() {
    this.array = [], this.size = 0
  }

  function c(a, b, c, d) {
    this.addCallback = a.bind(d), this.removeCallback = b.bind(d), this.changedCallback = c.bind(d), A && (this.observer = new A(this.mutationWatcher.bind(this)))
  }

  function d(a) {
    return "body /shadow-deep/ " + e(a)
  }

  function e(a) {
    return '[touch-action="' + a + '"]'
  }

  function f(a) {
    return "{ -ms-touch-action: " + a + "; touch-action: " + a + "; }"
  }

  function g() {
    if (F) {
      D.forEach(function (a) {
        String(a) === a ? (E += e(a) + f(a) + "\n", G && (E += d(a) + f(a) + "\n")) : (E += a.selectors.map(e) + f(a.rule) + "\n", G && (E += a.selectors.map(d) + f(a.rule) + "\n"))
      });
      var a = document.createElement("style");
      a.textContent = E, document.head.appendChild(a)
    }
  }

  function h() {
    if (!window.PointerEvent) {
      if (window.PointerEvent = a, window.navigator.msPointerEnabled) {
        var b = window.navigator.msMaxTouchPoints;
        Object.defineProperty(window.navigator, "maxTouchPoints", {
          value: b,
          enumerable: !0
        }), u.registerSource("ms", _)
      } else Object.defineProperty(window.navigator, "maxTouchPoints", {
        value: 0,
        enumerable: !0
      }), u.registerSource("mouse", N), void 0 !== window.ontouchstart && u.registerSource("touch", V);
      u.register(document)
    }
  }

  function i(a) {
    if (!u.pointermap.has(a)) {
      var b = new Error("InvalidPointerId");
      throw b.name = "InvalidPointerId", b
    }
  }

  function j(a) {
    for (var b = a.parentNode; b && b !== a.ownerDocument;) b = b.parentNode;
    if (!b) {
      var c = new Error("InvalidStateError");
      throw c.name = "InvalidStateError", c
    }
  }

  function k(a) {
    var b = u.pointermap.get(a);
    return 0 !== b.buttons
  }

  function l() {
    window.Element && !Element.prototype.setPointerCapture && Object.defineProperties(Element.prototype, {
      setPointerCapture: {
        value: W
      },
      releasePointerCapture: {
        value: X
      },
      hasPointerCapture: {
        value: Y
      }
    })
  }
  var m = ["bubbles", "cancelable", "view", "detail", "screenX", "screenY", "clientX", "clientY", "ctrlKey", "altKey", "shiftKey", "metaKey", "button", "relatedTarget", "pageX", "pageY"],
    n = [!1, !1, null, null, 0, 0, 0, 0, !1, !1, !1, !1, 0, null, 0, 0],
    o = window.Map && window.Map.prototype.forEach,
    p = o ? Map : b;
  b.prototype = {
    set: function (a, b) {
      return void 0 === b ? this["delete"](a) : (this.has(a) || this.size++, void(this.array[a] = b))
    },
    has: function (a) {
      return void 0 !== this.array[a]
    },
    "delete": function (a) {
      this.has(a) && (delete this.array[a], this.size--)
    },
    get: function (a) {
      return this.array[a]
    },
    clear: function () {
      this.array.length = 0, this.size = 0
    },
    forEach: function (a, b) {
      return this.array.forEach(function (c, d) {
        a.call(b, c, d, this)
      }, this)
    }
  };
  var q = ["bubbles", "cancelable", "view", "detail", "screenX", "screenY", "clientX", "clientY", "ctrlKey", "altKey", "shiftKey", "metaKey", "button", "relatedTarget", "buttons", "pointerId", "width", "height", "pressure", "tiltX", "tiltY", "pointerType", "hwTimestamp", "isPrimary", "type", "target", "currentTarget", "which", "pageX", "pageY", "timeStamp"],
    r = [!1, !1, null, null, 0, 0, 0, 0, !1, !1, !1, !1, 0, null, 0, 0, 0, 0, 0, 0, 0, "", 0, !1, "", null, null, 0, 0, 0, 0],
    s = {
      pointerover: 1,
      pointerout: 1,
      pointerenter: 1,
      pointerleave: 1
    },
    t = "undefined" != typeof SVGElementInstance,
    u = {
      pointermap: new p,
      eventMap: Object.create(null),
      captureInfo: Object.create(null),
      eventSources: Object.create(null),
      eventSourceList: [],
      registerSource: function (a, b) {
        var c = b,
          d = c.events;
        d && (d.forEach(function (a) {
          c[a] && (this.eventMap[a] = c[a].bind(c))
        }, this), this.eventSources[a] = c, this.eventSourceList.push(c))
      },
      register: function (a) {
        for (var b, c = this.eventSourceList.length, d = 0; d < c && (b = this.eventSourceList[d]); d++)
          b.register.call(b, a)
      },
      unregister: function (a) {
        for (var b, c = this.eventSourceList.length, d = 0; d < c && (b = this.eventSourceList[d]); d++)
          b.unregister.call(b, a)
      },
      contains: function (a, b) {
        try {
          return a.contains(b)
        } catch (c) {
          return !1
        }
      },
      down: function (a) {
        a.bubbles = !0, this.fireEvent("pointerdown", a)
      },
      move: function (a) {
        a.bubbles = !0, this.fireEvent("pointermove", a)
      },
      up: function (a) {
        a.bubbles = !0, this.fireEvent("pointerup", a)
      },
      enter: function (a) {
        a.bubbles = !1, this.fireEvent("pointerenter", a)
      },
      leave: function (a) {
        a.bubbles = !1, this.fireEvent("pointerleave", a)
      },
      over: function (a) {
        a.bubbles = !0, this.fireEvent("pointerover", a)
      },
      out: function (a) {
        a.bubbles = !0, this.fireEvent("pointerout", a)
      },
      cancel: function (a) {
        a.bubbles = !0, this.fireEvent("pointercancel", a)
      },
      leaveOut: function (a) {
        this.out(a), this.propagate(a, this.leave, !1)
      },
      enterOver: function (a) {
        this.over(a), this.propagate(a, this.enter, !0)
      },
      eventHandler: function (a) {
        if (!a._handledByPE) {
          var b = a.type,
            c = this.eventMap && this.eventMap[b];
          c && c(a), a._handledByPE = !0
        }
      },
      listen: function (a, b) {
        b.forEach(function (b) {
          this.addEvent(a, b)
        }, this)
      },
      unlisten: function (a, b) {
        b.forEach(function (b) {
          this.removeEvent(a, b)
        }, this)
      },
      addEvent: function (a, b) {
        a.addEventListener(b, this.boundHandler)
      },
      removeEvent: function (a, b) {
        a.removeEventListener(b, this.boundHandler)
      },
      makeEvent: function (b, c) {
        this.captureInfo[c.pointerId] && (c.relatedTarget = null);
        var d = new a(b, c);
        return c.preventDefault && (d.preventDefault = c.preventDefault), d._target = d._target || c.target, d
      },
      fireEvent: function (a, b) {
        var c = this.makeEvent(a, b);
        return this.dispatchEvent(c)
      },
      cloneEvent: function (a) {
        for (var b, c = Object.create(null), d = 0; d < q.length; d++) b = q[d], c[b] = a[b] || r[d], !t || "target" !== b && "relatedTarget" !== b || c[b] instanceof SVGElementInstance && (c[b] = c[b].correspondingUseElement);
        return a.preventDefault && (c.preventDefault = function () {
          a.preventDefault()
        }), c
      },
      getTarget: function (a) {
        var b = this.captureInfo[a.pointerId];
        return b ? a._target !== b && a.type in s ? void 0 : b : a._target
      },
      propagate: function (a, b, c) {
        for (var d = a.target, e = []; d !== document && !d.contains(a.relatedTarget);)
          if (e.push(d), d = d.parentNode, !d) return;
        c && e.reverse(), e.forEach(function (c) {
          a.target = c, b.call(this, a)
        }, this)
      },
      setCapture: function (b, c, d) {
        this.captureInfo[b] && this.releaseCapture(b, d), this.captureInfo[b] = c, this.implicitRelease = this.releaseCapture.bind(this, b, d), document.addEventListener("pointerup", this.implicitRelease), document.addEventListener("pointercancel", this.implicitRelease);
        var e = new a("gotpointercapture");
        e.pointerId = b, e._target = c, d || this.asyncDispatchEvent(e)
      },
      releaseCapture: function (b, c) {
        var d = this.captureInfo[b];
        if (d) {
          this.captureInfo[b] = void 0, document.removeEventListener("pointerup", this.implicitRelease), document.removeEventListener("pointercancel", this.implicitRelease);
          var e = new a("lostpointercapture");
          e.pointerId = b, e._target = d, c || this.asyncDispatchEvent(e)
        }
      },
      dispatchEvent: /*scope.external.dispatchEvent || */ function (a) {
        var b = this.getTarget(a);
        if (b) return b.dispatchEvent(a)
      },
      asyncDispatchEvent: function (a) {
        requestAnimationFrame(this.dispatchEvent.bind(this, a))
      }
    };
  u.boundHandler = u.eventHandler.bind(u);
  var v = {
      shadow: function (a) {
        if (a) return a.shadowRoot || a.webkitShadowRoot
      },
      canTarget: function (a) {
        return a && Boolean(a.elementFromPoint)
      },
      targetingShadow: function (a) {
        var b = this.shadow(a);
        if (this.canTarget(b)) return b
      },
      olderShadow: function (a) {
        var b = a.olderShadowRoot;
        if (!b) {
          var c = a.querySelector("shadow");
          c && (b = c.olderShadowRoot)
        }
        return b
      },
      allShadows: function (a) {
        for (var b = [], c = this.shadow(a); c;) b.push(c), c = this.olderShadow(c);
        return b
      },
      searchRoot: function (a, b, c) {
        if (a) {
          var d, e, f = a.elementFromPoint(b, c);
          for (e = this.targetingShadow(f); e;) {
            if (d = e.elementFromPoint(b, c)) {
              var g = this.targetingShadow(d);
              return this.searchRoot(g, b, c) || d
            }
            e = this.olderShadow(e)
          }
          return f
        }
      },
      owner: function (a) {
        for (var b = a; b.parentNode;) b = b.parentNode;
        return b.nodeType !== Node.DOCUMENT_NODE && b.nodeType !== Node.DOCUMENT_FRAGMENT_NODE && (b = document), b
      },
      findTarget: function (a) {
        var b = a.clientX,
          c = a.clientY,
          d = this.owner(a.target);
        return d.elementFromPoint(b, c) || (d = document), this.searchRoot(d, b, c)
      }
    },
    w = Array.prototype.forEach.call.bind(Array.prototype.forEach),
    x = Array.prototype.map.call.bind(Array.prototype.map),
    y = Array.prototype.slice.call.bind(Array.prototype.slice),
    z = Array.prototype.filter.call.bind(Array.prototype.filter),
    A = window.MutationObserver || window.WebKitMutationObserver,
    B = "[touch-action]",
    C = {
      subtree: !0,
      childList: !0,
      attributes: !0,
      attributeOldValue: !0,
      attributeFilter: ["touch-action"]
    };
  c.prototype = {
    watchSubtree: function (a) {
      //
      this.observer && v.canTarget(a) && this.observer.observe(a, C)
    },
    enableOnSubtree: function (a) {
      this.watchSubtree(a), a === document && "complete" !== document.readyState ? this.installOnLoad() : this.installNewSubtree(a)
    },
    installNewSubtree: function (a) {
      w(this.findElements(a), this.addElement, this)
    },
    findElements: function (a) {
      return a.querySelectorAll ? a.querySelectorAll(B) : []
    },
    removeElement: function (a) {
      this.removeCallback(a)
    },
    addElement: function (a) {
      this.addCallback(a)
    },
    elementChanged: function (a, b) {
      this.changedCallback(a, b)
    },
    concatLists: function (a, b) {
      return a.concat(y(b))
    },
    installOnLoad: function () {
      document.addEventListener("readystatechange", function () {
        "complete" === document.readyState && this.installNewSubtree(document)
      }.bind(this))
    },
    isElement: function (a) {
      return a.nodeType === Node.ELEMENT_NODE
    },
    flattenMutationTree: function (a) {
      var b = x(a, this.findElements, this);
      return b.push(z(a, this.isElement)), b.reduce(this.concatLists, [])
    },
    mutationWatcher: function (a) {
      a.forEach(this.mutationHandler, this)
    },
    mutationHandler: function (a) {
      if ("childList" === a.type) {
        var b = this.flattenMutationTree(a.addedNodes);
        b.forEach(this.addElement, this);
        var c = this.flattenMutationTree(a.removedNodes);
        c.forEach(this.removeElement, this)
      } else "attributes" === a.type && this.elementChanged(a.target, a.oldValue)
    }
  };
  var D = ["none", "auto", "pan-x", "pan-y", {
      rule: "pan-x pan-y",
      selectors: ["pan-x pan-y", "pan-y pan-x"]
    }],
    E = "",
    F = window.PointerEvent || window.MSPointerEvent,
    G = !window.ShadowDOMPolyfill && document.head.createShadowRoot,
    H = u.pointermap,
    I = 25,
    J = [1, 4, 2, 8, 16],
    K = !1;
  try {
    K = 1 === new MouseEvent("test", {
      buttons: 1
    }).buttons
  } catch (L) {}
  var M, N = {
      POINTER_ID: 1,
      POINTER_TYPE: "mouse",
      events: ["mousedown", "mousemove", "mouseup", "mouseover", "mouseout"],
      register: function (a) {
        u.listen(a, this.events)
      },
      unregister: function (a) {
        u.unlisten(a, this.events)
      },
      lastTouches: [],
      isEventSimulatedFromTouch: function (a) {
        for (var b, c = this.lastTouches, d = a.clientX, e = a.clientY, f = 0, g = c.length; f < g && (b = c[f]); f++) {
          var h = Math.abs(d - b.x),
            i = Math.abs(e - b.y);
          if (h <= I && i <= I) return !0
        }
      },
      prepareEvent: function (a) {
        var b = u.cloneEvent(a),
          c = b.preventDefault;
        return b.preventDefault = function () {
          a.preventDefault(), c()
        }, b.pointerId = this.POINTER_ID, b.isPrimary = !0, b.pointerType = this.POINTER_TYPE, b
      },
      prepareButtonsForMove: function (a, b) {
        var c = H.get(this.POINTER_ID);
        0 !== b.which && c ? a.buttons = c.buttons : a.buttons = 0, b.buttons = a.buttons
      },
      mousedown: function (a) {
        if (!this.isEventSimulatedFromTouch(a)) {
          var b = H.get(this.POINTER_ID),
            c = this.prepareEvent(a);
          K || (c.buttons = J[c.button], b && (c.buttons |= b.buttons), a.buttons = c.buttons), H.set(this.POINTER_ID, a), b && 0 !== b.buttons ? u.move(c) : u.down(c)
        }
      },
      mousemove: function (a) {
        if (!this.isEventSimulatedFromTouch(a)) {
          var b = this.prepareEvent(a);
          K || this.prepareButtonsForMove(b, a), b.button = -1, H.set(this.POINTER_ID, a), u.move(b)
        }
      },
      mouseup: function (a) {
        if (!this.isEventSimulatedFromTouch(a)) {
          var b = H.get(this.POINTER_ID),
            c = this.prepareEvent(a);
          if (!K) {
            var d = J[c.button];
            c.buttons = b ? b.buttons & ~d : 0, a.buttons = c.buttons
          }
          H.set(this.POINTER_ID, a),
            c.buttons &= ~J[c.button], 0 === c.buttons ? u.up(c) : u.move(c)
        }
      },
      mouseover: function (a) {
        if (!this.isEventSimulatedFromTouch(a)) {
          var b = this.prepareEvent(a);
          K || this.prepareButtonsForMove(b, a), b.button = -1, H.set(this.POINTER_ID, a), u.enterOver(b)
        }
      },
      mouseout: function (a) {
        if (!this.isEventSimulatedFromTouch(a)) {
          var b = this.prepareEvent(a);
          K || this.prepareButtonsForMove(b, a), b.button = -1, u.leaveOut(b)
        }
      },
      cancel: function (a) {
        var b = this.prepareEvent(a);
        u.cancel(b), this.deactivateMouse()
      },
      deactivateMouse: function () {
        H["delete"](this.POINTER_ID)
      }
    },
    O = u.captureInfo,
    P = v.findTarget.bind(v),
    Q = v.allShadows.bind(v),
    R = u.pointermap,
    S = 2500,
    T = 200,
    U = "touch-action",
    V = {
      events: ["touchstart", "touchmove", "touchend", "touchcancel"],
      register: function (a) {
        M.enableOnSubtree(a)
      },
      unregister: function () {},
      elementAdded: function (a) {
        var b = a.getAttribute(U),
          c = this.touchActionToScrollType(b);
        c && (a._scrollType = c, u.listen(a, this.events),
          Q(a).forEach(function (a) {
            a._scrollType = c, u.listen(a, this.events)
          }, this))
      },
      elementRemoved: function (a) {
        a._scrollType = void 0, u.unlisten(a, this.events),
          Q(a).forEach(function (a) {
            a._scrollType = void 0, u.unlisten(a, this.events)
          }, this)
      },
      elementChanged: function (a, b) {
        var c = a.getAttribute(U),
          d = this.touchActionToScrollType(c),
          e = this.touchActionToScrollType(b);
        d && e ? (a._scrollType = d, Q(a).forEach(function (a) {
          a._scrollType = d
        }, this)) : e ? this.elementRemoved(a) : d && this.elementAdded(a)
      },
      scrollTypes: {
        EMITTER: "none",
        XSCROLLER: "pan-x",
        YSCROLLER: "pan-y",
        SCROLLER: /^(?:pan-x pan-y)|(?:pan-y pan-x)|auto$/
      },
      touchActionToScrollType: function (a) {
        var b = a,
          c = this.scrollTypes;
        return "none" === b ? "none" : b === c.XSCROLLER ? "X" : b === c.YSCROLLER ? "Y" : c.SCROLLER.exec(b) ? "XY" : void 0
      },
      POINTER_TYPE: "touch",
      firstTouch: null,
      isPrimaryTouch: function (a) {
        return this.firstTouch === a.identifier
      },
      setPrimaryTouch: function (a) {
        (0 === R.size || 1 === R.size && R.has(1)) && (this.firstTouch = a.identifier, this.firstXY = {
          X: a.clientX,
          Y: a.clientY
        }, this.scrolling = !1, this.cancelResetClickCount())
      },
      removePrimaryPointer: function (a) {
        a.isPrimary && (this.firstTouch = null, this.firstXY = null, this.resetClickCount())
      },
      clickCount: 0,
      resetId: null,
      resetClickCount: function () {
        var a = function () {
          this.clickCount = 0, this.resetId = null
        }.bind(this);
        this.resetId = setTimeout(a, T)
      },
      cancelResetClickCount: function () {
        this.resetId && clearTimeout(this.resetId)
      },
      typeToButtons: function (a) {
        var b = 0;
        return "touchstart" !== a && "touchmove" !== a || (b = 1), b
      },
      touchToPointer: function (a) {
        var b = this.currentTouchEvent,
          c = u.cloneEvent(a),
          d = c.pointerId = a.identifier + 2;
        c.target = O[d] || P(c), c.bubbles = !0, c.cancelable = !0, c.detail = this.clickCount, c.button = 0, c.buttons = this.typeToButtons(b.type), c.width = 2 * (a.radiusX || a.webkitRadiusX || 0), c.height = 2 * (a.radiusY || a.webkitRadiusY || 0), c.pressure = a.force || a.webkitForce || .5, c.isPrimary = this.isPrimaryTouch(a), c.pointerType = this.POINTER_TYPE,
          c.altKey = b.altKey, c.ctrlKey = b.ctrlKey, c.metaKey = b.metaKey, c.shiftKey = b.shiftKey;
        var e = this;
        return c.preventDefault = function () {
          e.scrolling = !1, e.firstXY = null, b.preventDefault()
        }, c
      },
      processTouches: function (a, b) {
        var c = a.changedTouches;
        this.currentTouchEvent = a;
        for (var d, e = 0; e < c.length; e++) d = c[e], b.call(this, this.touchToPointer(d))
      },
      shouldScroll: function (a) {
        if (this.firstXY) {
          var b, c = a.currentTarget._scrollType;
          if ("none" === c)
            b = !1;
          else if ("XY" === c)
            b = !0;
          else {
            var d = a.changedTouches[0],
              e = c,
              f = "Y" === c ? "X" : "Y",
              g = Math.abs(d["client" + e] - this.firstXY[e]),
              h = Math.abs(d["client" + f] - this.firstXY[f]);
            b = g >= h
          }
          return this.firstXY = null, b
        }
      },
      findTouch: function (a, b) {
        for (var c, d = 0, e = a.length; d < e && (c = a[d]); d++)
          if (c.identifier === b) return !0
      },
      vacuumTouches: function (a) {
        var b = a.touches;
        if (R.size >= b.length) {
          var c = [];
          R.forEach(function (a, d) {
            if (1 !== d && !this.findTouch(b, d - 2)) {
              var e = a.out;
              c.push(e)
            }
          }, this), c.forEach(this.cancelOut, this)
        }
      },
      touchstart: function (a) {
        this.vacuumTouches(a), this.setPrimaryTouch(a.changedTouches[0]), this.dedupSynthMouse(a), this.scrolling || (this.clickCount++, this.processTouches(a, this.overDown))
      },
      overDown: function (a) {
        R.set(a.pointerId, {
          target: a.target,
          out: a,
          outTarget: a.target
        }), u.enterOver(a), u.down(a)
      },
      touchmove: function (a) {
        this.scrolling || (this.shouldScroll(a) ? (this.scrolling = !0, this.touchcancel(a)) : (a.preventDefault(), this.processTouches(a, this.moveOverOut)))
      },
      moveOverOut: function (a) {
        var b = a,
          c = R.get(b.pointerId);
        if (c) {
          var d = c.out,
            e = c.outTarget;
          u.move(b), d && e !== b.target && (d.relatedTarget = b.target, b.relatedTarget = e,
            d.target = e, b.target ? (u.leaveOut(d), u.enterOver(b)) : (
              b.target = e, b.relatedTarget = null, this.cancelOut(b))), c.out = b, c.outTarget = b.target
        }
      },
      touchend: function (a) {
        this.dedupSynthMouse(a), this.processTouches(a, this.upOut)
      },
      upOut: function (a) {
        this.scrolling || (u.up(a), u.leaveOut(a)), this.cleanUpPointer(a)
      },
      touchcancel: function (a) {
        this.processTouches(a, this.cancelOut)
      },
      cancelOut: function (a) {
        u.cancel(a), u.leaveOut(a), this.cleanUpPointer(a)
      },
      cleanUpPointer: function (a) {
        R["delete"](a.pointerId), this.removePrimaryPointer(a)
      },
      dedupSynthMouse: function (a) {
        var b = N.lastTouches,
          c = a.changedTouches[0];
        if (this.isPrimaryTouch(c)) {
          var d = {
            x: c.clientX,
            y: c.clientY
          };
          b.push(d);
          var e = function (a, b) {
            var c = a.indexOf(b);
            c > -1 && a.splice(c, 1)
          }.bind(null, b, d);
          setTimeout(e, S)
        }
      }
    };
  M = new c(V.elementAdded, V.elementRemoved, V.elementChanged, V);
  var W, X, Y, Z = u.pointermap,
    $ = window.MSPointerEvent && "number" == typeof window.MSPointerEvent.MSPOINTER_TYPE_MOUSE,
    _ = {
      events: ["MSPointerDown", "MSPointerMove", "MSPointerUp", "MSPointerOut", "MSPointerOver", "MSPointerCancel", "MSGotPointerCapture", "MSLostPointerCapture"],
      register: function (a) {
        u.listen(a, this.events)
      },
      unregister: function (a) {
        u.unlisten(a, this.events)
      },
      POINTER_TYPES: ["", "unavailable", "touch", "pen", "mouse"],
      prepareEvent: function (a) {
        var b = a;
        return $ && (b = u.cloneEvent(a), b.pointerType = this.POINTER_TYPES[a.pointerType]), b
      },
      cleanup: function (a) {
        Z["delete"](a)
      },
      MSPointerDown: function (a) {
        Z.set(a.pointerId, a);
        var b = this.prepareEvent(a);
        u.down(b)
      },
      MSPointerMove: function (a) {
        var b = this.prepareEvent(a);
        u.move(b)
      },
      MSPointerUp: function (a) {
        var b = this.prepareEvent(a);
        u.up(b), this.cleanup(a.pointerId)
      },
      MSPointerOut: function (a) {
        var b = this.prepareEvent(a);
        u.leaveOut(b)
      },
      MSPointerOver: function (a) {
        var b = this.prepareEvent(a);
        u.enterOver(b)
      },
      MSPointerCancel: function (a) {
        var b = this.prepareEvent(a);
        u.cancel(b), this.cleanup(a.pointerId)
      },
      MSLostPointerCapture: function (a) {
        var b = u.makeEvent("lostpointercapture", a);
        u.dispatchEvent(b)
      },
      MSGotPointerCapture: function (a) {
        var b = u.makeEvent("gotpointercapture", a);
        u.dispatchEvent(b)
      }
    },
    aa = window.navigator;
  aa.msPointerEnabled ? (W = function (a) {
    i(a), j(this), k(a) && (u.setCapture(a, this, !0), this.msSetPointerCapture(a))
  }, X = function (a) {
    i(a), u.releaseCapture(a, !0), this.msReleasePointerCapture(a)
  }) : (W = function (a) {
    i(a), j(this), k(a) && u.setCapture(a, this)
  }, X = function (a) {
    i(a), u.releaseCapture(a)
  }), Y = function (a) {
    return !!u.captureInfo[a]
  }, g(), h(), l();
  var ba = {
    dispatcher: u,
    Installer: c,
    PointerEvent: a,
    PointerMap: p,
    targetFinding: v
  };
  return ba
});
///////////////////////////////////////////////

///////////////////////////////////////////////
var config = {"show_fabrication":false,"redraw_on_drag":true,"highlight_pin1":false,"extra_fields":["BOM_Manufacturer","BOM_Manufacturer Part","BOM_Supplier","BOM_Supplier Part"],"dark_mode":false,"bom_view":"left-right","board_rotation":0,"checkboxes":"Sourced,Placed","show_silkscreen":true,"show_pads":true,"layer_view":"FB"};
///////////////////////////////////////////////

///////////////////////////////////////////////
var pcbdata = {"ibom_version":"v2.3-50-g53ae\n","edges_bbox":{"minx":3989.6,"miny":3375.6,"maxx":4051.6,"maxy":3449.4},"edges":[{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4031.8699,3384.5864 4008.1301,3384.5864"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4041.6535,3418.051 4041.6535,3402.6966"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4038.1102,3436.1612 4038.1102,3420.0195 4039.685,3420.0195"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4003.8583,3438.1297 4036.1417,3438.1297"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4000.315,3420.0195 4001.8898,3420.0195 4001.8898,3436.1612"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M3998.3465,3402.6966 3998.3465,3418.051"},{"type":"arc","width":1,"svgpath":"M 4008.1301 3384.5864 A 21.6535 21.6535 0 0 0 3998.3465 3402.6966","net":""},{"type":"arc","width":1,"svgpath":"M 3998.3465 3418.051 A 1.9685 1.9685 0 0 0 4000.315 3420.0195","net":""},{"type":"arc","width":1,"svgpath":"M 4001.8898 3436.1612 A 1.9685 1.9685 0 0 0 4003.8583 3438.1297","net":""},{"type":"arc","width":1,"svgpath":"M 4036.1417 3438.1297 A 1.9685 1.9685 0 0 0 4038.1102 3436.1612","net":""},{"type":"arc","width":1,"svgpath":"M 4039.685 3420.0195 A 1.9685 1.9685 0 0 0 4041.6535 3418.051","net":""},{"type":"arc","width":1,"svgpath":"M 4041.6535 3402.6966 A 21.6535 21.6535 0 0 0 4031.8699 3384.5864","net":""}],"drawings":{"silkscreen":{"F":[{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4039.4489,3410.1781 4039.4489,3413.3278"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4035.0798,3408.9582 4038.2293,3408.9582"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4035.0798,3414.5474 4038.2293,3414.5474"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4032.8125,3408.9353 4029.6629,3408.9353"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4028.4431,3410.1551 4028.4431,3413.3046"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4032.8125,3414.5243 4029.6629,3414.5243"},{"type":"arc","width":1,"svgpath":"M 4028.4429 3410.1551 A 1.2198 1.2198 0 0 1 4029.6627 3408.9353","net":""},{"type":"arc","width":1,"svgpath":"M 4029.6627 3414.5245 A 1.2198 1.2198 0 0 1 4028.4429 3413.3046","net":""},{"type":"arc","width":1,"svgpath":"M 4038.2291 3408.9582 A 1.2198 1.2198 0 0 1 4039.4489 3410.1781","net":""},{"type":"arc","width":1,"svgpath":"M 4039.4489 3413.3276 A 1.2198 1.2198 0 0 1 4038.2291 3414.5474","net":""},{"type":"text","svgpath":"M 4032.9764 3397.032 L 4033.3864 3396.822 L 4033.9964 3396.212 L 4033.9964 3400.502 M 4036.5764 3396.212 L 4035.9664 3396.412 L 4035.5564 3397.032 L 4035.3464 3398.052 L 4035.3464 3398.662 L 4035.5564 3399.692 L 4035.9664 3400.302 L 4036.5764 3400.502 L 4036.9864 3400.502 L 4037.5964 3400.302 L 4038.0064 3399.692 L 4038.2164 3398.662 L 4038.2164 3398.052 L 4038.0064 3397.032 L 4037.5964 3396.412 L 4036.9864 3396.212 L 4036.5764 3396.212 M 4040.7864 3396.212 L 4040.1764 3396.412 L 4039.7664 3397.032 L 4039.5664 3398.052 L 4039.5664 3398.662 L 4039.7664 3399.692 L 4040.1764 3400.302 L 4040.7864 3400.502 L 4041.1964 3400.502 L 4041.8164 3400.302 L 4042.2264 3399.692 L 4042.4264 3398.662 L 4042.4264 3398.052 L 4042.2264 3397.032 L 4041.8164 3396.412 L 4041.1964 3396.212 L 4040.7864 3396.212 M 4043.7764 3397.642 L 4043.7764 3400.502 M 4043.7764 3398.462 L 4044.3864 3397.852 L 4044.7964 3397.642 L 4045.4164 3397.642 L 4045.8264 3397.852 L 4046.0264 3398.462 L 4046.0264 3400.502 M 4047.3764 3396.212 L 4047.3764 3400.502 M 4047.3764 3396.212 L 4050.0364 3396.212 M 4047.3764 3398.252 L 4049.0164 3398.252","useTrueTypeFontRendering":false,"thickness":0.6,"width":0.6,"net":"","val":1},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":0.6,"svgpath":"M4020.2905,3408.9543 4016.5154,3408.9543 4016.5154,3414.1559 4020.2905,3414.1559"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":0.6,"svgpath":"M4023.6465,3408.9543 4027.4216,3408.9543 4027.4216,3414.1559 4023.6465,3414.1559"},{"type":"text","svgpath":"M 4012.5185 3423.6251 L 4012.9285 3423.4151 L 4013.5385 3422.8051 L 4013.5385 3427.0951 M 4016.1185 3422.8051 L 4015.5085 3423.0051 L 4015.0985 3423.6251 L 4014.8885 3424.6451 L 4014.8885 3425.2551 L 4015.0985 3426.2851 L 4015.5085 3426.8951 L 4016.1185 3427.0951 L 4016.5285 3427.0951 L 4017.1385 3426.8951 L 4017.5485 3426.2851 L 4017.7585 3425.2551 L 4017.7585 3424.6451 L 4017.5485 3423.6251 L 4017.1385 3423.0051 L 4016.5285 3422.8051 L 4016.1185 3422.8051 M 4019.1085 3422.8051 L 4019.1085 3427.0951 M 4021.9685 3422.8051 L 4019.1085 3425.6651 M 4020.1285 3424.6451 L 4021.9685 3427.0951","useTrueTypeFontRendering":false,"thickness":0.6,"width":0.6,"net":"","val":1},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":0.6,"svgpath":"M4011.6387,3414.1559 4015.4138,3414.1559 4015.4138,3408.9543 4011.6387,3408.9543"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":0.6,"svgpath":"M4008.2827,3414.1559 4004.5076,3414.1559 4004.5076,3408.9543 4008.2827,3408.9543"},{"type":"text","svgpath":"M 4009.9607 3396.8351 L 4010.3707 3396.6251 L 4010.9807 3396.0151 L 4010.9807 3400.3051 M 4013.5607 3396.0151 L 4012.9507 3396.2151 L 4012.5407 3396.8351 L 4012.3307 3397.8551 L 4012.3307 3398.4651 L 4012.5407 3399.4951 L 4012.9507 3400.1051 L 4013.5607 3400.3051 L 4013.9707 3400.3051 L 4014.5807 3400.1051 L 4014.9907 3399.4951 L 4015.2007 3398.4651 L 4015.2007 3397.8551 L 4014.9907 3396.8351 L 4014.5807 3396.2151 L 4013.9707 3396.0151 L 4013.5607 3396.0151 M 4016.5507 3396.0151 L 4016.5507 3400.3051 M 4019.4107 3396.0151 L 4016.5507 3398.8751 M 4017.5707 3397.8551 L 4019.4107 3400.3051","useTrueTypeFontRendering":false,"thickness":0.6,"width":0.6,"net":"","val":1},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4035.941,3437.238 4004.0518,3437.238"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4004.0518,3417.553 4011.6148,3417.553"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4028.3948,3417.553 4035.941,3417.553"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4035.941,3417.553 4035.941,3423.333"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4035.941,3433.0329 4035.941,3437.238"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4004.0518,3417.553 4004.0518,3423.333"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4004.0518,3433.0329 4004.0518,3437.238"},{"type":"circle","_svgpath":"M 4010.508, 3415.842 m -0.501, 0 a 0.501,0.501 0 1,0 1.002,0 a 0.501,0.501 0 1,0 -1.002,0","start":[4010.508,3415.842],"radius":0.501,"width":"1","net":""},{"type":"text","svgpath":"M 3960.59 3445.99L3960.59 3450.29 M 3959.16 3445.99L3962.02 3445.99 M 3963.37 3445.99L3963.37 3450.29 M 3966.23 3445.99L3966.23 3450.29 M 3963.37 3448.04L3966.23 3448.04 M 3967.58 3445.99L3967.58 3450.29 M 3967.58 3445.99L3969.02 3445.99 L3969.63 3446.2 L3970.04 3446.6 L3970.24 3447.01 L3970.45 3447.63 L3970.45 3448.65 L3970.24 3449.26 L3970.04 3449.67 L3969.63 3450.08 L3969.02 3450.29 L3967.58 3450.29 M 3973.02 3445.99L3972.41 3446.2 L3972 3446.81 L3971.8 3447.83 L3971.8 3448.45 L3972 3449.47 L3972.41 3450.08 L3973.02 3450.29 L3973.43 3450.29 L3974.05 3450.08 L3974.46 3449.47 L3974.66 3448.45 L3974.66 3447.83 L3974.46 3446.81 L3974.05 3446.2 L3973.43 3445.99 L3973.02 3445.99 M 3978.47 3445.99L3976.42 3445.99 L3976.22 3447.83 L3976.42 3447.63 L3977.03 3447.42 L3977.65 3447.42 L3978.26 3447.63 L3978.67 3448.04 L3978.87 3448.65 L3978.87 3449.06 L3978.67 3449.67 L3978.26 3450.08 L3977.65 3450.29 L3977.03 3450.29 L3976.42 3450.08 L3976.22 3449.88 L3976.01 3449.47 M 3980.22 3446.81L3980.63 3446.6 L3981.25 3445.99 L3981.25 3450.29 M 3985.05 3445.99L3983.01 3445.99 L3982.8 3447.83 L3983.01 3447.63 L3983.62 3447.42 L3984.23 3447.42 L3984.85 3447.63 L3985.26 3448.04 L3985.46 3448.65 L3985.46 3449.06 L3985.26 3449.67 L3984.85 3450.08 L3984.23 3450.29 L3983.62 3450.29 L3983.01 3450.08 L3982.8 3449.88 L3982.6 3449.47 M 3986.81 3448.45L3990.49 3448.45 M 3993.07 3445.99L3992.46 3446.2 L3992.05 3446.81 L3991.84 3447.83 L3991.84 3448.45 L3992.05 3449.47 L3992.46 3450.08 L3993.07 3450.29 L3993.48 3450.29 L3994.09 3450.08 L3994.5 3449.47 L3994.71 3448.45 L3994.71 3447.83 L3994.5 3446.81 L3994.09 3446.2 L3993.48 3445.99 L3993.07 3445.99 M 3997.08 3445.99L3996.47 3446.2 L3996.26 3446.6 L3996.26 3447.01 L3996.47 3447.42 L3996.87 3447.63 L3997.69 3447.83 L3998.31 3448.04 L3998.72 3448.45 L3998.92 3448.85 L3998.92 3449.47 L3998.72 3449.88 L3998.51 3450.08 L3997.9 3450.29 L3997.08 3450.29 L3996.47 3450.08 L3996.26 3449.88 L3996.06 3449.47 L3996.06 3448.85 L3996.26 3448.45 L3996.67 3448.04 L3997.28 3447.83 L3998.1 3447.63 L3998.51 3447.42 L3998.72 3447.01 L3998.72 3446.6 L3998.51 3446.2 L3997.9 3445.99 L3997.08 3445.99 M 4003.34 3447.01L4003.13 3446.6 L4002.72 3446.2 L4002.32 3445.99 L4001.5 3445.99 L4001.09 3446.2 L4000.68 3446.6 L4000.47 3447.01 L4000.27 3447.63 L4000.27 3448.65 L4000.47 3449.26 L4000.68 3449.67 L4001.09 3450.08 L4001.5 3450.29 L4002.32 3450.29 L4002.72 3450.08 L4003.13 3449.67 L4003.34 3449.26 M 4004.69 3445.99L4004.69 3450.29 M 4004.69 3450.29L4007.14 3450.29 M 4008.49 3448.45L4012.17 3448.45 M 4016.39 3446.6L4015.98 3446.2 L4015.37 3445.99 L4014.55 3445.99 L4013.93 3446.2 L4013.52 3446.6 L4013.52 3447.01 L4013.73 3447.42 L4013.93 3447.63 L4014.34 3447.83 L4015.57 3448.24 L4015.98 3448.45 L4016.18 3448.65 L4016.39 3449.06 L4016.39 3449.67 L4015.98 3450.08 L4015.37 3450.29 L4014.55 3450.29 L4013.93 3450.08 L4013.52 3449.67 M 4017.74 3445.99L4017.74 3450.29 M 4017.74 3445.99L4020.6 3450.29 M 4020.6 3445.99L4020.6 3450.29 ","useTrueTypeFontRendering":false,"thickness":0.6,"width":0.6,"net":"","val":1},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4033.9139,3396.9119 4031.6448,3399.181 4029.8077,3397.344"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4025.3529,3392.8892 4023.5159,3391.0521 4025.3529,3392.8892 4023.5159,3391.0521 4025.785,3388.783"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":0.7874,"svgpath":"M4032.7302,3391.6369 4031.06,3389.9667"},{"type":"text","svgpath":"M 4011.3457 3360.2545 L 4011.3457 3364.5445 M 4009.9157 3360.2545 L 4012.7757 3360.2545 M 4014.1257 3360.2545 L 4014.1257 3364.5445 M 4014.1257 3364.5445 L 4016.5857 3364.5445 M 4017.9357 3360.2545 L 4017.9357 3364.5445 M 4017.9357 3360.2545 L 4020.5957 3360.2545 M 4017.9357 3362.2945 L 4019.5657 3362.2945 M 4017.9357 3364.5445 L 4020.5957 3364.5445 M 4023.9857 3360.2545 L 4021.9457 3363.1145 L 4025.0157 3363.1145 M 4023.9857 3360.2545 L 4023.9857 3364.5445 M 4029.0157 3361.6845 L 4028.8157 3362.2945 L 4028.4057 3362.7045 L 4027.7957 3362.9145 L 4027.5857 3362.9145 L 4026.9757 3362.7045 L 4026.5657 3362.2945 L 4026.3657 3361.6845 L 4026.3657 3361.4845 L 4026.5657 3360.8645 L 4026.9757 3360.4545 L 4027.5857 3360.2545 L 4027.7957 3360.2545 L 4028.4057 3360.4545 L 4028.8157 3360.8645 L 4029.0157 3361.6845 L 4029.0157 3362.7045 L 4028.8157 3363.7345 L 4028.4057 3364.3445 L 4027.7957 3364.5445 L 4027.3857 3364.5445 L 4026.7657 3364.3445 L 4026.5657 3363.9345 M 4032.8257 3360.8645 L 4032.6157 3360.4545 L 4032.0057 3360.2545 L 4031.5957 3360.2545 L 4030.9857 3360.4545 L 4030.5757 3361.0745 L 4030.3657 3362.0945 L 4030.3657 3363.1145 L 4030.5757 3363.9345 L 4030.9857 3364.3445 L 4031.5957 3364.5445 L 4031.8057 3364.5445 L 4032.4157 3364.3445 L 4032.8257 3363.9345 L 4033.0257 3363.3245 L 4033.0257 3363.1145 L 4032.8257 3362.5045 L 4032.4157 3362.0945 L 4031.8057 3361.8945 L 4031.5957 3361.8945 L 4030.9857 3362.0945 L 4030.5757 3362.5045 L 4030.3657 3363.1145 M 4036.4257 3360.2545 L 4034.3757 3363.1145 L 4037.4457 3363.1145 M 4036.4257 3360.2545 L 4036.4257 3364.5445 M 4041.2557 3360.8645 L 4041.0457 3360.4545 L 4040.4357 3360.2545 L 4040.0257 3360.2545 L 4039.4157 3360.4545 L 4039.0057 3361.0745 L 4038.7957 3362.0945 L 4038.7957 3363.1145 L 4039.0057 3363.9345 L 4039.4157 3364.3445 L 4040.0257 3364.5445 L 4040.2257 3364.5445 L 4040.8457 3364.3445 L 4041.2557 3363.9345 L 4041.4557 3363.3245 L 4041.4557 3363.1145 L 4041.2557 3362.5045 L 4040.8457 3362.0945 L 4040.2257 3361.8945 L 4040.0257 3361.8945 L 4039.4157 3362.0945 L 4039.0057 3362.5045 L 4038.7957 3363.1145 M 4042.8057 3360.2545 L 4042.8057 3364.5445 M 4042.8057 3360.2545 L 4044.4457 3364.5445 M 4046.0757 3360.2545 L 4044.4457 3364.5445 M 4046.0757 3360.2545 L 4046.0757 3364.5445 M 4047.4257 3360.2545 L 4050.2957 3364.5445 M 4050.2957 3360.2545 L 4047.4257 3364.5445 M 4053.0757 3360.2545 L 4053.0757 3364.5445 M 4051.6457 3360.2545 L 4054.5057 3360.2545 M 4055.8557 3360.2545 L 4055.8557 3364.5445 M 4055.8557 3360.2545 L 4057.4957 3364.5445 M 4059.1257 3360.2545 L 4057.4957 3364.5445 M 4059.1257 3360.2545 L 4059.1257 3364.5445 M 4062.1157 3360.2545 L 4060.4757 3364.5445 M 4062.1157 3360.2545 L 4063.7557 3364.5445 M 4061.0957 3363.1145 L 4063.1357 3363.1145 M 4065.1057 3361.0745 L 4065.5157 3360.8645 L 4066.1257 3360.2545 L 4066.1257 3364.5445","useTrueTypeFontRendering":false,"thickness":0.6,"width":0.6,"net":"","val":1},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4014.2157,3388.7837 4016.4848,3391.0528 4014.6478,3392.8899"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4010.193,3397.3447 4008.3559,3399.1817 4010.193,3397.3447 4008.3559,3399.1817 4006.0868,3396.9126"},{"type":"polyline","net":"","start":[0,0],"end":[0,0],"width":0.7874,"svgpath":"M4008.9407,3389.9674 4007.2705,3391.6376"},{"type":"text","svgpath":"M 3977.559 3410.6385 L 3981.859 3410.6385 M 3977.559 3412.0685 L 3977.559 3409.2085 M 3977.559 3407.8585 L 3981.859 3407.8585 M 3981.859 3407.8585 L 3981.859 3405.3985 M 3977.559 3404.0485 L 3981.859 3404.0485 M 3977.559 3404.0485 L 3977.559 3401.3885 M 3979.609 3404.0485 L 3979.609 3402.4185 M 3981.859 3404.0485 L 3981.859 3401.3885 M 3977.559 3397.9985 L 3980.419 3400.0385 L 3980.419 3396.9785 M 3977.559 3397.9985 L 3981.859 3397.9985 M 3978.989 3392.9685 L 3979.609 3393.1685 L 3980.009 3393.5785 L 3980.219 3394.1885 L 3980.219 3394.3985 L 3980.009 3395.0085 L 3979.609 3395.4185 L 3978.989 3395.6285 L 3978.789 3395.6285 L 3978.169 3395.4185 L 3977.759 3395.0085 L 3977.559 3394.3985 L 3977.559 3394.1885 L 3977.759 3393.5785 L 3978.169 3393.1685 L 3978.989 3392.9685 L 3980.009 3392.9685 L 3981.039 3393.1685 L 3981.649 3393.5785 L 3981.859 3394.1885 L 3981.859 3394.5985 L 3981.649 3395.2185 L 3981.239 3395.4185 M 3978.169 3389.1585 L 3977.759 3389.3685 L 3977.559 3389.9785 L 3977.559 3390.3885 L 3977.759 3390.9985 L 3978.379 3391.4085 L 3979.399 3391.6185 L 3980.419 3391.6185 L 3981.239 3391.4085 L 3981.649 3390.9985 L 3981.859 3390.3885 L 3981.859 3390.1785 L 3981.649 3389.5685 L 3981.239 3389.1585 L 3980.629 3388.9585 L 3980.419 3388.9585 L 3979.809 3389.1585 L 3979.399 3389.5685 L 3979.199 3390.1785 L 3979.199 3390.3885 L 3979.399 3390.9985 L 3979.809 3391.4085 L 3980.419 3391.6185 M 3977.559 3385.5585 L 3980.419 3387.6085 L 3980.419 3384.5385 M 3977.559 3385.5585 L 3981.859 3385.5585 M 3978.169 3380.7285 L 3977.759 3380.9385 L 3977.559 3381.5485 L 3977.559 3381.9585 L 3977.759 3382.5785 L 3978.379 3382.9785 L 3979.399 3383.1885 L 3980.419 3383.1885 L 3981.239 3382.9785 L 3981.649 3382.5785 L 3981.859 3381.9585 L 3981.859 3381.7585 L 3981.649 3381.1385 L 3981.239 3380.7285 L 3980.629 3380.5285 L 3980.419 3380.5285 L 3979.809 3380.7285 L 3979.399 3381.1385 L 3979.199 3381.7585 L 3979.199 3381.9585 L 3979.399 3382.5785 L 3979.809 3382.9785 L 3980.419 3383.1885 M 3977.559 3379.1785 L 3981.859 3379.1785 M 3977.559 3379.1785 L 3981.859 3377.5385 M 3977.559 3375.9085 L 3981.859 3377.5385 M 3977.559 3375.9085 L 3981.859 3375.9085 M 3977.559 3374.5585 L 3981.859 3371.6885 M 3977.559 3371.6885 L 3981.859 3374.5585 M 3977.559 3368.9085 L 3981.859 3368.9085 M 3977.559 3370.3385 L 3977.559 3367.4785 M 3977.559 3366.1285 L 3981.859 3366.1285 M 3977.559 3366.1285 L 3981.859 3364.4885 M 3977.559 3362.8585 L 3981.859 3364.4885 M 3977.559 3362.8585 L 3981.859 3362.8585 M 3977.559 3359.8685 L 3981.859 3361.5085 M 3977.559 3359.8685 L 3981.859 3358.2285 M 3980.419 3360.8885 L 3980.419 3358.8485 M 3978.379 3356.8785 L 3978.169 3356.4785 L 3977.559 3355.8585 L 3981.859 3355.8585","useTrueTypeFontRendering":false,"thickness":0.6,"width":0.6,"net":"","val":1},{"type":"text","svgpath":"M 4018.0848 3390 L 4020.1248 3390 L 4020.1248 3389.65 L 4018.5048 3389.65 L 4018.5048 3386.34 L 4018.0848 3386.34 Z  M 4020.7648 3390 L 4022.9048 3390 L 4022.9048 3389.65 L 4021.1848 3389.65 L 4021.1848 3388.24 L 4022.5848 3388.24 L 4022.5848 3387.89 L 4021.1848 3387.89 L 4021.1848 3386.69 L 4022.8448 3386.69 L 4022.8448 3386.34 L 4020.7648 3386.34 Z ","useTrueTypeFontRendering":true,"thickness":0.8,"width":0.8,"net":""}],"B":[{"type":"text","svgpath":"M4028.85 3397L4028.37 3397L4027.41 3394.30L4027.81 3394.30L4028.34 3395.87C4028.42 3396.14 4028.51 3396.41 4028.60 3396.66L4028.62 3396.66C4028.70 3396.41 4028.79 3396.14 4028.87 3395.87L4029.41 3394.30L4029.83 3394.30ZM4026.90 3397L4024.93 3397L4024.93 3396.66L4025.67 3396.66L4025.67 3393.34L4025.98 3393.34C4026.17 3393.45 4026.41 3393.53 4026.72 3393.59L4026.72 3393.86L4026.08 3393.86L4026.08 3396.66L4026.90 3396.66ZM4023.93 3397.07C4023.76 3397.07 4023.62 3396.93 4023.62 3396.74C4023.62 3396.55 4023.76 3396.41 4023.93 3396.41C4024.09 3396.41 4024.23 3396.55 4024.23 3396.74C4024.23 3396.93 4024.09 3397.07 4023.93 3397.07ZM4023.02 3397L4020.76 3397L4020.76 3396.65L4021.81 3396.65C4021.99 3396.65 4022.21 3396.66 4022.41 3396.68C4021.51 3395.84 4020.93 3395.09 4020.93 3394.34C4020.93 3393.70 4021.33 3393.28 4021.98 3393.28C4022.44 3393.28 4022.75 3393.49 4023.05 3393.81L4022.81 3394.04C4022.60 3393.80 4022.33 3393.61 4022.03 3393.61C4021.55 3393.61 4021.33 3393.93 4021.33 3394.36C4021.33 3394.99 4021.85 3395.74 4023.02 3396.76ZM4020.28 3397L4018.01 3397L4018.01 3396.65L4019.06 3396.65C4019.25 3396.65 4019.47 3396.66 4019.66 3396.68C4018.77 3395.84 4018.19 3395.09 4018.19 3394.34C4018.19 3393.70 4018.59 3393.28 4019.24 3393.28C4019.69 3393.28 4020.01 3393.49 4020.30 3393.81L4020.06 3394.04C4019.85 3393.80 4019.59 3393.61 4019.28 3393.61C4018.81 3393.61 4018.59 3393.93 4018.59 3394.36C4018.59 3394.99 4019.11 3395.74 4020.28 3396.76ZM4016.38 3397.07C4015.70 3397.07 4015.27 3396.43 4015.27 3395.16C4015.27 3393.89 4015.70 3393.28 4016.38 3393.28C4017.07 3393.28 4017.51 3393.89 4017.51 3395.16C4017.51 3396.43 4017.07 3397.07 4016.38 3397.07ZM4016.38 3396.74C4016.82 3396.74 4017.11 3396.24 4017.11 3395.16C4017.11 3394.09 4016.82 3393.61 4016.38 3393.61C4015.95 3393.61 4015.66 3394.09 4015.66 3395.16C4015.66 3396.24 4015.95 3396.74 4016.38 3396.74ZM4014.40 3394.45C4014.40 3393.95 4014.12 3393.59 4013.76 3393.59C4013.26 3393.59 4012.98 3394.03 4012.93 3394.80C4013.18 3395.15 4013.47 3395.30 4013.72 3395.30C4014.18 3395.30 4014.40 3394.96 4014.40 3394.45ZM4013.86 3397.07C4013.18 3397.07 4012.54 3396.50 4012.54 3395C4012.54 3393.86 4013.05 3393.28 4013.76 3393.28C4014.32 3393.28 4014.79 3393.76 4014.79 3394.45C4014.79 3395.21 4014.40 3395.61 4013.79 3395.61C4013.47 3395.61 4013.16 3395.42 4012.93 3395.14C4012.96 3396.32 4013.39 3396.72 4013.87 3396.72C4014.11 3396.72 4014.33 3396.62 4014.49 3396.44L4014.72 3396.70C4014.52 3396.91 4014.24 3397.07 4013.86 3397.07Z","useTrueTypeFontRendering":true,"thickness":0.8,"width":0.8,"net":""},{"type":"text","svgpath":"M 4028.742 3391.1733 L 4026.702 3391.1733 L 4026.702 3390.8233 L 4028.322 3390.8233 L 4028.322 3387.5133 L 4028.742 3387.5133 Z  M 4026.102 3391.1733 L 4025.692 3391.1733 L 4025.692 3388.4733 L 4026.102 3388.4733 Z  M 4025.892 3387.8933 C 4025.732 3387.8933 4025.612 3387.7833 4025.612 3387.6033 C 4025.612 3387.4433 4025.732 3387.3333 4025.892 3387.3333 C 4026.062 3387.3333 4026.182 3387.4433 4026.182 3387.6033 C 4026.182 3387.7833 4026.062 3387.8933 4025.892 3387.8933 Z  M 4024.752 3391.1733 L 4024.352 3391.1733 L 4024.352 3389.2033 C 4024.092 3388.9033 4023.852 3388.7633 4023.642 3388.7633 C 4023.282 3388.7633 4023.112 3388.9933 4023.112 3389.5133 L 4023.112 3391.1733 L 4022.712 3391.1733 L 4022.712 3389.2033 C 4022.452 3388.9033 4022.222 3388.7633 4022.002 3388.7633 C 4021.642 3388.7633 4021.472 3388.9933 4021.472 3389.5133 L 4021.472 3391.1733 L 4021.062 3391.1733 L 4021.062 3389.4533 C 4021.062 3388.7633 4021.332 3388.4033 4021.882 3388.4033 C 4022.212 3388.4033 4022.492 3388.6233 4022.782 3388.9333 C 4022.892 3388.6033 4023.112 3388.4033 4023.532 3388.4033 C 4023.842 3388.4033 4024.132 3388.6133 4024.372 3388.8733 L 4024.382 3388.8733 L 4024.422 3388.4733 L 4024.752 3388.4733 Z  M 4020.162 3391.1733 L 4019.752 3391.1733 L 4019.752 3388.4733 L 4020.162 3388.4733 Z  M 4019.952 3387.8933 C 4019.792 3387.8933 4019.672 3387.7833 4019.672 3387.6033 C 4019.672 3387.4433 4019.792 3387.3333 4019.952 3387.3333 C 4020.122 3387.3333 4020.242 3387.4433 4020.242 3387.6033 C 4020.242 3387.7833 4020.122 3387.8933 4019.952 3387.8933 Z  M 4017.992 3391.2433 C 4017.842 3391.2433 4017.662 3391.1833 4017.512 3391.1433 L 4017.592 3390.8333 C 4017.682 3390.8733 4017.812 3390.9033 4017.912 3390.9033 C 4018.232 3390.9033 4018.332 3390.7033 4018.332 3390.3733 L 4018.332 3388.8133 L 4017.592 3388.8133 L 4017.592 3388.4733 L 4018.332 3388.4733 L 4018.332 3387.7033 L 4018.672 3387.7033 L 4018.722 3388.4733 L 4019.152 3388.4933 L 4019.152 3388.8133 L 4018.742 3388.8133 L 4018.742 3390.3533 C 4018.742 3390.8833 4018.562 3391.2433 4017.992 3391.2433 Z  M 4015.812 3391.2433 C 4015.192 3391.2433 4014.632 3390.7033 4014.632 3389.7833 C 4014.632 3388.9533 4015.012 3388.4033 4015.712 3388.4033 C 4016.022 3388.4033 4016.332 3388.5833 4016.582 3388.7933 L 4016.562 3388.3033 L 4016.562 3387.1933 L 4016.972 3387.1933 L 4016.972 3391.1733 L 4016.652 3391.1733 L 4016.602 3390.8933 L 4016.592 3390.8933 C 4016.352 3391.1033 4016.062 3391.2433 4015.812 3391.2433 Z  M 4015.872 3390.8933 C 4016.062 3390.8933 4016.312 3390.8133 4016.562 3390.6033 L 4016.562 3389.1433 C 4016.292 3388.8833 4016.042 3388.7433 4015.812 3388.7433 C 4015.272 3388.7433 4015.062 3389.1733 4015.062 3389.7833 C 4015.062 3390.4733 4015.402 3390.8933 4015.872 3390.8933 Z  M 4013.902 3391.1733 L 4013.492 3391.1733 L 4013.492 3388.4733 L 4013.902 3388.4733 Z  M 4013.692 3387.8933 C 4013.532 3387.8933 4013.412 3387.7833 4013.412 3387.6033 C 4013.412 3387.4433 4013.532 3387.3333 4013.692 3387.3333 C 4013.862 3387.3333 4013.982 3387.4433 4013.982 3387.6033 C 4013.982 3387.7833 4013.862 3387.8933 4013.692 3387.8933 Z  M 4011.732 3391.2433 C 4011.582 3391.2433 4011.402 3391.1833 4011.252 3391.1433 L 4011.332 3390.8333 C 4011.422 3390.8733 4011.552 3390.9033 4011.652 3390.9033 C 4011.972 3390.9033 4012.072 3390.7033 4012.072 3390.3733 L 4012.072 3388.8133 L 4011.332 3388.8133 L 4011.332 3388.4733 L 4012.072 3388.4733 L 4012.072 3387.7033 L 4012.412 3387.7033 L 4012.462 3388.4733 L 4012.892 3388.4933 L 4012.892 3388.8133 L 4012.482 3388.8133 L 4012.482 3390.3533 C 4012.482 3390.8833 4012.302 3391.2433 4011.732 3391.2433 Z ","useTrueTypeFontRendering":true,"thickness":0.8,"width":0.8,"net":""},{"type":"text","svgpath":"M 4032.0033 3435.9838 C 4031.6233 3435.9838 4031.2733 3435.8938 4030.9533 3435.7038 L 4031.0633 3435.4538 C 4031.3233 3435.6038 4031.6333 3435.7038 4031.9733 3435.7038 C 4032.9333 3435.7038 4033.6433 3435.0738 4033.6433 3433.9938 C 4033.6433 3432.6838 4032.6733 3431.8338 4031.6833 3431.8338 C 4030.6833 3431.8338 4030.1333 3432.4738 4030.1333 3433.3938 C 4030.1333 3434.1338 4030.5433 3434.5638 4030.8933 3434.5638 C 4031.2133 3434.5638 4031.3233 3434.3538 4031.2033 3433.8938 L 4030.9933 3432.7938 L 4031.2733 3432.7938 L 4031.3233 3433.0238 L 4031.3333 3433.0238 C 4031.4433 3432.8338 4031.5933 3432.7438 4031.7833 3432.7438 C 4032.4433 3432.7438 4032.8533 3433.4538 4032.8533 3434.0238 C 4032.8533 3434.5338 4032.5633 3434.8138 4032.1933 3434.8138 C 4031.9433 3434.8138 4031.6933 3434.6438 4031.5133 3434.4338 L 4031.5033 3434.4338 C 4031.4633 3434.7138 4031.2333 3434.8538 4030.9333 3434.8538 C 4030.4333 3434.8538 4029.8433 3434.3438 4029.8433 3433.3738 C 4029.8433 3432.2938 4030.5333 3431.5438 4031.6533 3431.5438 C 4032.8733 3431.5438 4033.9533 3432.5238 4033.9533 3434.0038 C 4033.9533 3435.2838 4033.0933 3435.9838 4032.0033 3435.9838 Z  M 4032.1133 3434.5238 C 4032.3433 3434.5238 4032.5233 3434.3738 4032.5233 3434.0038 C 4032.5233 3433.5738 4032.2433 3433.0438 4031.7933 3433.0438 C 4031.6333 3433.0438 4031.5233 3433.1038 4031.4133 3433.2738 L 4031.5733 3434.1838 C 4031.7733 3434.4238 4031.9533 3434.5238 4032.1133 3434.5238 Z  M 4029.0833 3435.1338 L 4028.6733 3435.1338 L 4028.6733 3433.3738 C 4028.4933 3432.9038 4028.2133 3432.7338 4027.9833 3432.7338 C 4027.8733 3432.7338 4027.8133 3432.7438 4027.7233 3432.7738 L 4027.6433 3432.4238 C 4027.7333 3432.3738 4027.8133 3432.3638 4027.9333 3432.3638 C 4028.2333 3432.3638 4028.5133 3432.5838 4028.6933 3432.9338 L 4028.7133 3432.9338 L 4028.7533 3432.4338 L 4029.0833 3432.4338 Z  M 4026.2033 3435.2038 C 4025.5533 3435.2038 4024.9633 3434.6838 4024.9633 3433.7838 C 4024.9633 3432.8838 4025.5533 3432.3638 4026.2033 3432.3638 C 4026.8733 3432.3638 4027.4533 3432.8838 4027.4533 3433.7838 C 4027.4533 3434.6838 4026.8733 3435.2038 4026.2033 3435.2038 Z  M 4026.2033 3434.8538 C 4026.6933 3434.8538 4027.0333 3434.4338 4027.0333 3433.7838 C 4027.0333 3433.1438 4026.6933 3432.7038 4026.2033 3432.7038 C 4025.7233 3432.7038 4025.3833 3433.1438 4025.3833 3433.7838 C 4025.3833 3434.4338 4025.7233 3434.8538 4026.2033 3434.8538 Z  M 4023.0733 3435.2038 C 4022.4433 3435.2038 4021.8933 3434.6638 4021.8933 3433.7438 C 4021.8933 3432.9138 4022.2633 3432.3638 4022.9733 3432.3638 C 4023.2833 3432.3638 4023.5933 3432.5438 4023.8433 3432.7538 L 4023.8233 3432.2638 L 4023.8233 3431.1538 L 4024.2233 3431.1538 L 4024.2233 3435.1338 L 4023.9033 3435.1338 L 4023.8633 3434.8538 L 4023.8533 3434.8538 C 4023.6133 3435.0638 4023.3233 3435.2038 4023.0733 3435.2038 Z  M 4023.1333 3434.8538 C 4023.3133 3434.8538 4023.5733 3434.7738 4023.8233 3434.5638 L 4023.8233 3433.1038 C 4023.5533 3432.8438 4023.3033 3432.7038 4023.0633 3432.7038 C 4022.5233 3432.7038 4022.3133 3433.1338 4022.3133 3433.7438 C 4022.3133 3434.4338 4022.6633 3434.8538 4023.1333 3434.8538 Z  M 4020.1233 3435.2038 C 4019.4633 3435.2038 4018.8833 3434.6838 4018.8833 3433.7838 C 4018.8833 3432.8838 4019.4633 3432.3638 4020.1233 3432.3638 C 4020.7833 3432.3638 4021.3633 3432.8838 4021.3633 3433.7838 C 4021.3633 3434.6838 4020.7833 3435.2038 4020.1233 3435.2038 Z  M 4020.1233 3434.8538 C 4020.6133 3434.8538 4020.9433 3434.4338 4020.9433 3433.7838 C 4020.9433 3433.1438 4020.6133 3432.7038 4020.1233 3432.7038 C 4019.6433 3432.7038 4019.3033 3433.1438 4019.3033 3433.7838 C 4019.3033 3434.4338 4019.6433 3434.8538 4020.1233 3434.8538 Z  M 4017.4133 3435.2038 C 4017.2633 3435.2038 4017.0833 3435.1438 4016.9333 3435.1038 L 4017.0133 3434.7938 C 4017.1033 3434.8338 4017.2333 3434.8638 4017.3333 3434.8638 C 4017.6533 3434.8638 4017.7533 3434.6638 4017.7533 3434.3338 L 4017.7533 3432.7738 L 4017.0133 3432.7738 L 4017.0133 3432.4338 L 4017.7533 3432.4338 L 4017.7533 3431.6638 L 4018.0933 3431.6638 L 4018.1433 3432.4338 L 4018.5733 3432.4538 L 4018.5733 3432.7738 L 4018.1633 3432.7738 L 4018.1633 3434.3138 C 4018.1633 3434.8438 4017.9833 3435.2038 4017.4133 3435.2038 Z  M 4015.5533 3435.2038 C 4015.2233 3435.2038 4014.9333 3435.0238 4014.7233 3434.8138 L 4014.7033 3434.8138 L 4014.6733 3435.1338 L 4014.3333 3435.1338 L 4014.3333 3431.1538 L 4014.7433 3431.1538 L 4014.7433 3432.2138 L 4014.7233 3432.6838 C 4014.9633 3432.4838 4015.1733 3432.3638 4015.4933 3432.3638 C 4016.1133 3432.3638 4016.6733 3432.9138 4016.6733 3433.7838 C 4016.6733 3434.6838 4016.2333 3435.2038 4015.5533 3435.2038 Z  M 4015.4733 3434.8538 C 4015.9633 3434.8538 4016.2433 3434.4538 4016.2433 3433.7838 C 4016.2433 3433.1538 4015.8933 3432.7038 4015.4333 3432.7038 C 4015.1933 3432.7038 4014.9833 3432.7938 4014.7433 3433.0138 L 4014.7433 3434.4738 C 4014.9833 3434.7238 4015.2133 3434.8538 4015.4733 3434.8538 Z  M 4012.3633 3435.2038 C 4011.7033 3435.2038 4011.1133 3434.6838 4011.1133 3433.7838 C 4011.1133 3432.8838 4011.7033 3432.3638 4012.3633 3432.3638 C 4013.0233 3432.3638 4013.6033 3432.8838 4013.6033 3433.7838 C 4013.6033 3434.6838 4013.0233 3435.2038 4012.3633 3435.2038 Z  M 4012.3633 3434.8538 C 4012.8433 3434.8538 4013.1833 3434.4338 4013.1833 3433.7838 C 4013.1833 3433.1438 4012.8433 3432.7038 4012.3633 3432.7038 C 4011.8833 3432.7038 4011.5333 3433.1438 4011.5333 3433.7838 C 4011.5333 3434.4338 4011.8833 3434.8538 4012.3633 3434.8538 Z  M 4009.9433 3435.2038 C 4009.8233 3435.2038 4009.7633 3435.1838 4009.6933 3435.1638 L 4009.7533 3434.8538 C 4009.8033 3434.8538 4009.8233 3434.8538 4009.8533 3434.8538 C 4009.9233 3434.8538 4009.9733 3434.8038 4009.9733 3434.6638 L 4009.9733 3431.1538 L 4010.3833 3431.1538 L 4010.3833 3434.6438 C 4010.3833 3434.9938 4010.2533 3435.2038 4009.9433 3435.2038 Z  M 4008.9833 3436.3138 C 4008.4533 3436.3138 4008.1733 3435.8938 4007.9933 3435.3838 L 4006.9633 3432.4338 L 4007.3633 3432.4338 L 4007.8733 3433.9738 C 4007.9433 3434.2138 4008.0233 3434.4938 4008.1033 3434.7438 L 4008.1233 3434.7438 C 4008.2133 3434.4938 4008.3133 3434.2138 4008.3933 3433.9738 L 4008.9733 3432.4338 L 4009.3933 3432.4338 L 4008.3033 3435.1438 L 4008.3633 3435.3538 C 4008.4833 3435.7038 4008.6833 3435.9738 4008.9933 3435.9738 C 4009.0733 3435.9738 4009.1533 3435.9538 4009.2033 3435.9338 L 4009.2933 3436.2638 C 4009.2033 3436.2938 4009.1033 3436.3138 4008.9833 3436.3138 Z ","useTrueTypeFontRendering":true,"thickness":0.8,"width":0.8,"net":""}]},"fabrication":{"F":[],"B":[]}},"footprints":[{"ref":"C1","center":[4033.9764,3411.752],"bbox":{"pos":[4033.9764,3411.752],"angle":0,"relpos":[-5.533500004105008,-2.816699999999855],"size":[11.006000008209867,5.612099999999828]},"pads":[{"layers":["F"],"pos":[4036.732,3411.752],"size":[3.1496,3.5433],"angle":0,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4036.7319,"y":3411.752},"polygon":[{"x":4035.1575,"y":3409.9803},{"x":4038.3071,"y":3409.9803},{"x":4038.3071,"y":3413.5237},{"x":4035.1575,"y":3413.5237}],"net":"GND"},{"layers":["F"],"pos":[4031.221,3411.752],"size":[3.1496,3.5433],"angle":0,"pin1":1,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4031.2209,"y":3411.752},"polygon":[{"x":4029.6457,"y":3409.9803},{"x":4032.7953,"y":3409.9803},{"x":4032.7953,"y":3413.5237},{"x":4029.6457,"y":3413.5237}],"net":"VCC"}],"drawings":[],"layer":"F"},{"ref":"R1","center":[4021.9685,3411.5551],"bbox":{"pos":[4021.9685,3411.5551],"angle":180,"relpos":[-5.4531000000006316,-2.6008000000006177],"size":[10.906199999999899,5.201600000000326]},"pads":[{"layers":["F"],"pos":[4019.003,3411.555],"size":[3.1751,3.4016],"angle":-180,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4019.003,"y":3411.5551},"polygon":[{"x":4020.5905,"y":3409.8543},{"x":4020.5905,"y":3413.2559},{"x":4017.4154,"y":3413.2559},{"x":4017.4154,"y":3409.8543}],"net":"EA"},{"layers":["F"],"pos":[4024.934,3411.555],"size":[3.1751,3.4016],"angle":-180,"pin1":1,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4024.934,"y":3411.5551},"polygon":[{"x":4023.3465,"y":3409.8543},{"x":4023.3465,"y":3413.2559},{"x":4026.5216,"y":3413.2559},{"x":4026.5216,"y":3409.8543}],"net":"VCC"}],"drawings":[],"layer":"F"},{"ref":"R2","center":[4009.9607,3411.5551],"bbox":{"pos":[4009.9607,3411.5551],"angle":0,"relpos":[-5.453100000000177,-2.600800000000163],"size":[10.906199999999899,5.201600000000326]},"pads":[{"layers":["F"],"pos":[4012.926,3411.555],"size":[3.1751,3.4016],"angle":0,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4012.9262,"y":3411.5551},"polygon":[{"x":4011.3387,"y":3413.2559},{"x":4011.3387,"y":3409.8543},{"x":4014.5138,"y":3409.8543},{"x":4014.5138,"y":3413.2559}],"net":"EB"},{"layers":["F"],"pos":[4006.995,3411.555],"size":[3.1751,3.4016],"angle":0,"pin1":1,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4006.9952,"y":3411.5551},"polygon":[{"x":4008.5827,"y":3413.2559},{"x":4008.5827,"y":3409.8543},{"x":4005.4076,"y":3409.8543},{"x":4005.4076,"y":3413.2559}],"net":"VCC"}],"drawings":[],"layer":"F"},{"ref":"CONN","center":[4019.996,3423.1645],"bbox":{"pos":[4019.996,3423.1645],"angle":180,"relpos":[-18.897750000000087,-14.073499999999513],"size":[37.79549999999972,21.89700015330982]},"pads":[{"layers":["F"],"pos":[4013.115,3418.147],"size":[1.1811,5.1181],"angle":0,"pin1":1,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4013.1152,"y":3418.1465},"polygon":[{"x":4012.5247,"y":3415.5875},{"x":4013.7058,"y":3415.5875},{"x":4013.7058,"y":3420.7056},{"x":4012.5247,"y":3420.7056}],"net":"VCC"},{"layers":["F"],"pos":[4015.083,3418.147],"size":[1.1811,5.1181],"angle":0,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4015.0832,"y":3418.1466},"polygon":[{"x":4014.4927,"y":3415.5875},{"x":4015.6738,"y":3415.5875},{"x":4015.6738,"y":3420.7056},{"x":4014.4927,"y":3420.7056}],"net":"GND"},{"layers":["F"],"pos":[4017.052,3418.147],"size":[1.1811,5.1181],"angle":0,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4017.0522,"y":3418.1466},"polygon":[{"x":4016.4617,"y":3415.5875},{"x":4017.6428,"y":3415.5875},{"x":4017.6428,"y":3420.7056},{"x":4016.4617,"y":3420.7056}],"net":"EB"},{"layers":["F"],"pos":[4019.02,3418.147],"size":[1.1811,5.1181],"angle":0,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4019.0202,"y":3418.1466},"polygon":[{"x":4018.4297,"y":3415.5875},{"x":4019.6108,"y":3415.5875},{"x":4019.6108,"y":3420.7056},{"x":4018.4297,"y":3420.7056}],"net":"EA"},{"layers":["F"],"pos":[4020.989,3418.147],"size":[1.1811,5.1181],"angle":0,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4020.9892,"y":3418.1466},"polygon":[{"x":4020.3987,"y":3415.5875},{"x":4021.5798,"y":3415.5875},{"x":4021.5798,"y":3420.7056},{"x":4020.3987,"y":3420.7056}],"net":"M2"},{"layers":["F"],"pos":[4022.957,3418.147],"size":[1.1811,5.1181],"angle":0,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4022.9572,"y":3418.1466},"polygon":[{"x":4022.3667,"y":3415.5875},{"x":4023.5478,"y":3415.5875},{"x":4023.5478,"y":3420.7056},{"x":4022.3667,"y":3420.7056}],"net":"M2"},{"layers":["F"],"pos":[4024.926,3418.147],"size":[1.1811,5.1181],"angle":0,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4024.9262,"y":3418.1466},"polygon":[{"x":4024.3357,"y":3415.5875},{"x":4025.5168,"y":3415.5875},{"x":4025.5168,"y":3420.7056},{"x":4024.3357,"y":3420.7056}],"net":"M1"},{"layers":["F"],"pos":[4026.894,3418.146],"size":[1.1811,5.1181],"angle":0,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4026.8942,"y":3418.1465},"polygon":[{"x":4026.3037,"y":3415.5874},{"x":4027.4848,"y":3415.5874},{"x":4027.4848,"y":3420.7055},{"x":4026.3037,"y":3420.7055}],"net":"M1"},{"layers":["F"],"pos":[4035.941,3428.183],"size":[5.9055,7.874],"angle":0,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4035.9412,"y":3428.1834},"polygon":[{"x":4032.9864,"y":3424.2429},{"x":4038.8964,"y":3424.2429},{"x":4038.8964,"y":3432.1229},{"x":4032.9864,"y":3432.1229}],"net":"GND"},{"layers":["F"],"pos":[4004.051,3428.183],"size":[5.9055,7.874],"angle":0,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4004.0512,"y":3428.1834},"polygon":[{"x":4001.0958,"y":3424.2429},{"x":4007.0058,"y":3424.2429},{"x":4007.0058,"y":3432.1229},{"x":4001.0958,"y":3432.1229}],"net":"GND"}],"drawings":[],"layer":"F"},{"ref":"U1","center":[4029.3898,3393.3071],"bbox":{"pos":[4029.3898,3393.3071],"angle":45,"relpos":[-7.634136941724137,-6.45411714527927],"size":[15.267991040735978,12.908375711916506]},"pads":[{"layers":["F"],"pos":[4025.911,3396.786],"size":[2.7559,4.9213],"angle":-315,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4025.9108,"y":3396.7861},"polygon":[{"x":4026.6763,"y":3394.0718},{"x":4028.6251,"y":3396.0206},{"x":4025.1453,"y":3399.5004},{"x":4023.1965,"y":3397.5516}],"net":"GND"},{"layers":["F"],"pos":[4030.224,3387.184],"size":[2.7559,4.9213],"angle":-315,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4030.2242,"y":3387.1836},"polygon":[{"x":4030.9897,"y":3384.4693},{"x":4032.9385,"y":3386.418},{"x":4029.4587,"y":3389.8979},{"x":4027.5099,"y":3387.9491}],"net":"EA"},{"layers":["F"],"pos":[4035.513,3392.473],"size":[2.7559,4.9213],"angle":-315,"pin1":1,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4035.5133,"y":3392.4727},"polygon":[{"x":4036.2789,"y":3389.7584},{"x":4038.2276,"y":3391.7072},{"x":4034.7478,"y":3395.187},{"x":4032.799,"y":3393.2382}],"net":"VCC"}],"drawings":[],"layer":"F"},{"ref":"U2","center":[4010.6109,3393.3078],"bbox":{"pos":[4010.6109,3393.3078],"angle":135,"relpos":[-7.634561205793034,-6.454258566636327],"size":[15.269405254297908,12.909082818697698]},"pads":[{"layers":["F"],"pos":[4014.09,3396.787],"size":[2.7559,4.9213],"angle":-45,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4014.0899,"y":3396.7868},"polygon":[{"x":4011.3756,"y":3396.0213},{"x":4013.3244,"y":3394.0725},{"x":4016.8042,"y":3397.5523},{"x":4014.8554,"y":3399.5011}],"net":"GND"},{"layers":["F"],"pos":[4004.487,3392.473],"size":[2.7559,4.9213],"angle":-45,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4004.4874,"y":3392.4734},"polygon":[{"x":4001.7731,"y":3391.7079},{"x":4003.7218,"y":3389.7591},{"x":4007.2017,"y":3393.2389},{"x":4005.2529,"y":3395.1877}],"net":"EB"},{"layers":["F"],"pos":[4009.777,3387.184],"size":[2.7559,4.9213],"angle":-45,"pin1":1,"shape":"rect","type":"smd","drillsize":[0,0],"holeCenterPoint":{"x":4009.7765,"y":3387.1843},"polygon":[{"x":4007.0622,"y":3386.4187},{"x":4009.011,"y":3384.47},{"x":4012.4908,"y":3387.9498},{"x":4010.542,"y":3389.8986}],"net":"VCC"}],"drawings":[],"layer":"F"}],"metadata":{"title":"PCB_Encoder 0922","revision":"0","company":"Unknown Company","date":"2023-04-05"},"tracks":{"F":[{"type":"polyline","net":"M1","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4026.8979,3418.1514 4026.8979,3423.76 4026.496,3423.76"},{"type":"polyline","net":"VCC","start":[0,0],"end":[0,0],"width":1.1811,"svgpath":"M4007.6001,3418.45 4007.6001,3421.2 4015.8701,3429.47 4027.6799,3429.47 4030.6299,3426.52 4030.6299,3420.6101 4031.22,3420.02 4031.22,3411.752"},{"type":"polyline","net":"M2","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4022.3622,3423.7598 4022.961,3423.161 4022.961,3418.1513"},{"type":"polyline","net":"M2","start":[0,0],"end":[0,0],"width":2.3622,"svgpath":"M4022.362,3423.76 4022.3621,3423.7601 4022.3621,3426.5161"},{"type":"polyline","net":"M2","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4022.362,3426.516 4020.9929,3425.1469 4020.9929,3418.1509"},{"type":"polyline","net":"GND","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4015.0869,3418.1509 4015.079,3418.1588 4015.079,3422.972"},{"type":"polyline","net":"VCC","start":[0,0],"end":[0,0],"width":1.1811,"svgpath":"M4013.1189,3418.1509 4007.8931,3418.1509 4007.599,3418.445"},{"type":"polyline","net":"GND","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4020,3393.248 4017.6292,3393.248 4014.0901,3396.7871"},{"type":"polyline","net":"GND","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4020,3393.248 4022.4909,3393.248 4026.167,3396.9241"},{"type":"polyline","net":"VCC","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4031.22,3411.752 4024.9351,3411.752 4024.9351,3411.5549"},{"type":"polyline","net":"EB","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4017.1001,3418.1509 4017.1001,3414.9543 4013.7008,3411.555 4012.927,3411.555"},{"type":"polyline","net":"VCC","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4007.5991,3418.4451 4007.5991,3412.1591 4006.995,3411.555"},{"type":"polyline","net":"M2","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4022.362,3426.516 4020.9929,3425.1469 4020.9929,3418.1509"},{"type":"polyline","net":"M1","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4026.5,3426.52 4024.93,3424.95 4024.93,3418.15"},{"type":"polyline","net":"M1","start":[0,0],"end":[0,0],"width":2.3622,"svgpath":"M4026.4961,3426.5161 4026.4961,3423.7601 4026.496,3423.76"},{"type":"polyline","net":"GND","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4011.732,3430.256 4009.659,3428.183 4004.051,3428.183"},{"type":"polyline","net":"GND","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4011.732,3430.256 4015.6689,3434.1929 4030.8268,3434.1929 4035.941,3429.0787 4035.941,3428.183"},{"type":"polyline","net":"EA","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4019.02,3418.15 4019,3411.56"},{"type":"polyline","net":"GND","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4036.732,3411.752 4036.732,3415.8862 4035.1575,3417.4607 4035.1575,3427.3997 4035.9409,3428.1831"},{"type":"polyline","net":"VCC","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4014.09,3401.91 4014.09,3400.73 4009.78,3396.41 4009.78,3387.18"},{"type":"polyline","net":"EB","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4012.93,3411.55 4012.93,3404.68 4011.14,3402.89 4011.14,3400.53 4004.49,3393.88 4004.49,3392.47"},{"type":"polyline","net":"EA","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4019,3411.56 4025.51,3405.05 4025.51,3400.92 4030.22,3396.21 4030.22,3387.18"},{"type":"polyline","net":"VCC","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4024.93,3411.56 4024.93,3408.4 4027.28,3406.04 4027.28,3401.89 4035.77,3393.4 4035.77,3392.6"},{"start":[4007.599,3418.445],"end":[4007.599,3418.445],"width":2.4,"net":"VCC"},{"start":[4020,3393.248],"end":[4020,3393.248],"width":2.4,"net":"GND"},{"start":[4015.079,3422.972],"end":[4015.079,3422.972],"width":2.4,"net":"GND"},{"start":[4026.496,3423.76],"end":[4026.496,3423.76],"width":2.4,"net":"M1"},{"start":[4022.362,3426.516],"end":[4022.362,3426.516],"width":2.4,"net":"M2"},{"start":[4022.362,3423.76],"end":[4022.362,3423.76],"width":2.4,"net":"M2"},{"start":[4026.496,3426.516],"end":[4026.496,3426.516],"width":2.4,"net":"M1"},{"start":[4014.095,3401.91],"end":[4014.095,3401.91],"width":2.4,"net":"VCC"},{"start":[4011.732,3430.256],"end":[4011.732,3430.256],"width":2.4,"net":"GND"}],"B":[{"type":"polyline","net":"M1","start":[0,0],"end":[0,0],"width":2.3622,"svgpath":"M4005.8269,3402.5 4011.3386,3396.9883 4022.4619,3396.9883 4026.496,3401.0224 4026.496,3426.516"},{"type":"polyline","net":"GND","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4011.7324,3430.2559 4015.079,3426.9093 4015.079,3422.972"},{"type":"polyline","net":"M1","start":[0,0],"end":[0,0],"width":1.9685,"svgpath":"M4026.4961,3426.5161 4026.4961,3423.7601 4026.496,3423.76"},{"type":"polyline","net":"M2","start":[0,0],"end":[0,0],"width":2.3622,"svgpath":"M4022.3621,3426.5161 4022.3621,3423.7601 4022.362,3423.76"},{"type":"polyline","net":"GND","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4001.2993,3405.2559 4001.2993,3398.563 4006.6143,3393.248 4020,3393.248"},{"type":"polyline","net":"M2","start":[0,0],"end":[0,0],"width":2.3622,"svgpath":"M4033.98,3402.5 4033.98,3425.92 4029.45,3430.45 4022.36,3430.45 4022.362,3423.76"},{"type":"polyline","net":"GND","start":[0,0],"end":[0,0],"width":1,"svgpath":"M4015.08,3422.97 4007.4,3422.97 4001.3,3416.87 4001.3,3405.26"},{"type":"polyline","net":"VCC","start":[0,0],"end":[0,0],"width":2.3622,"svgpath":"M4007.5991,3418.4451 4014.095,3411.9492 4014.095,3401.91"},{"start":[4007.599,3418.445],"end":[4007.599,3418.445],"width":2.4,"net":"VCC"},{"start":[4020,3393.248],"end":[4020,3393.248],"width":2.4,"net":"GND"},{"start":[4015.079,3422.972],"end":[4015.079,3422.972],"width":2.4,"net":"GND"},{"start":[4026.496,3423.76],"end":[4026.496,3423.76],"width":2.4,"net":"M1"},{"start":[4022.362,3426.516],"end":[4022.362,3426.516],"width":2.4,"net":"M2"},{"start":[4022.362,3423.76],"end":[4022.362,3423.76],"width":2.4,"net":"M2"},{"start":[4026.496,3426.516],"end":[4026.496,3426.516],"width":2.4,"net":"M1"},{"start":[4014.095,3401.91],"end":[4014.095,3401.91],"width":2.4,"net":"VCC"},{"start":[4011.732,3430.256],"end":[4011.732,3430.256],"width":2.4,"net":"GND"}]},"zones":{"F":[{"net":"GND","svgpath":"M 4016.06644 3399.90628 L 4015.90327 3399.87978 4015.76475 3399.78957 4014.46797 3398.49278 4014.34228 3398.55025 4014.20454 3398.56152 4014.07118 3398.52523 4013.95813 3398.44572 4012.43209 3396.91614 4012.35322 3396.8037 4012.31703 3396.6712 4012.3278 3396.53427 4012.38425 3396.40906 4011.41716 3395.44197 4011.33045 3395.3122 4011.3 3395.15913 4011.3 3390.74887 4011.33045 3390.59579 4011.41716 3390.46603 4013.19616 3388.68702 4013.35232 3388.49121 4013.45117 3388.28593 4013.50187 3388.0638 4013.50187 3387.83596 4013.45117 3387.61384 4013.35232 3387.40856 4013.19616 3387.21275 4012.27266 3386.28924 4012.18595 3386.15947 4012.1555 3386.0064 4012.18595 3385.85333 4012.27266 3385.72356 4012.40243 3385.63685 4012.5555 3385.6064 4027.4445 3385.6064 4027.59757 3385.63685 4027.72734 3385.72356 4027.81405 3385.85333 4027.8445 3386.0064 4027.81405 3386.15947 4027.72734 3386.28924 4026.80454 3387.21205 4026.64838 3387.40786 4026.54953 3387.61314 4026.49883 3387.83526 4026.49883 3388.0631 4026.54953 3388.28523 4026.64838 3388.49051 4026.80454 3388.68632 4028.58284 3390.46463 4028.66955 3390.59439 4028.7 3390.74747 4028.7 3395.15913 4028.66955 3395.3122 4028.58284 3395.44197 4027.57522 3396.44959 4027.58049 3396.58097 4027.54288 3396.70696 4027.46645 3396.81394 4025.93864 3398.34175 4025.83166 3398.41818 4025.70567 3398.45579 4025.57429 3398.45052 4024.23535 3399.78946 4024.09684 3399.87967 4023.93367 3399.90617 4023.77372 3399.86443 4023.64431 3399.76158 4023.32603 3399.37685 4022.93902 3399.01342 4022.847 3398.88357 4022.81289 3398.72812 4022.8421 3398.57167 4022.93 3398.43899 4024.2001 3397.16889 4023.16845 3396.13724 4022.49114 3396.81455 4022.33498 3397.01036 4022.23613 3397.21564 4022.18543 3397.43776 4022.18543 3397.6656 4022.21772 3397.80708 4022.21896 3397.97948 4022.14751 3398.1364 4022.01666 3398.24866 4021.85071 3398.29543 4021.6805 3398.268 4021.40999 3398.1609 4020.85503 3398.01841 4020.28658 3397.94659 4019.71362 3397.94659 4019.14517 3398.01841 4018.59021 3398.1609 4018.32043 3398.26771 4018.15021 3398.29514 4017.98426 3398.24838 4017.85341 3398.13611 4017.78197 3397.9792 4017.78321 3397.80679 4017.81527 3397.6663 4017.81527 3397.43846 4017.76457 3397.21634 4017.66572 3397.01106 4017.50956 3396.81525 4016.83225 3396.13794 4015.8006 3397.16959 4017.0701 3398.43909 4017.158 3398.57177 4017.18721 3398.72822 4017.1531 3398.88367 4017.06108 3399.01352 4016.67417 3399.37685 4016.3558 3399.76169 4016.22639 3399.86454 4016.06644 3399.90628 Z M 4025.26194 3394.04375 L 4026.29359 3395.0754 4027.70804 3393.66095 4027.41352 3393.36644 4027.21771 3393.21028 4027.01243 3393.11143 4026.7903 3393.06073 4026.56246 3393.06073 4026.34034 3393.11143 4026.13506 3393.21028 4025.93925 3393.36644 4025.26194 3394.04375 Z M 4013.70711 3395.0761 L 4014.73876 3394.04445 4014.06145 3393.36714 4013.86564 3393.21098 4013.66036 3393.11213 4013.43824 3393.06143 4013.2104 3393.06143 4012.98827 3393.11213 4012.78299 3393.21098 4012.58718 3393.36714 4012.29266 3393.66165 4013.70711 3395.0761 Z "},{"net":"GND","svgpath":"M 4020.07838 3427.82765 L 4019.92209 3427.85945 4016.7029 3427.85945 4016.54982 3427.829 4016.42005 3427.74229 4009.32781 3420.65005 4009.2411 3420.52028 4009.21065 3420.3672 4009.21065 3420.16145 4009.2411 3420.00838 4009.32781 3419.87861 4009.45758 3419.7919 4009.61065 3419.76145 4011.10465 3419.76145 4011.25772 3419.7919 4011.38749 3419.87861 4011.4742 3420.00838 4011.50465 3420.16145 4011.50465 3420.68309 4011.53269 3420.93196 4011.60794 3421.14701 4011.72916 3421.33993 4011.89027 3421.50104 4012.08319 3421.62226 4012.29824 3421.69751 4012.54711 3421.72555 4013.68329 3421.72555 4013.93216 3421.69751 4014.09906 3421.66289 4014.26624 3421.69761 4014.53793 3421.72565 4014.53793 3421.39678 4014.55356 3421.28605 4014.62246 3421.14701 4014.70613 3420.90789 4014.79046 3420.76794 4014.92373 3420.67338 4015.08368 3420.64001 4015.24364 3420.67338 4015.3769 3420.76794 4015.46124 3420.90789 4015.54494 3421.14711 4015.61284 3421.28456 4015.62847 3421.39529 4015.62847 3421.72565 4015.90016 3421.69761 4016.0677 3421.66276 4016.23524 3421.69761 4016.48411 3421.72565 4017.62029 3421.72565 4017.86916 3421.69761 4018.0362 3421.66294 4018.20324 3421.69761 4018.45211 3421.72565 4019.0729 3421.72565 4019.22597 3421.7561 4019.35574 3421.84281 4019.44245 3421.97258 4019.4729 3422.12565 4019.4729 3425.12725 4019.504 3425.44306 4019.59039 3425.72784 4019.73068 3425.99029 4019.93199 3426.2356 4020.03398 3426.33758 4020.11366 3426.45138 4020.14961 3426.58556 4020.17723 3426.90123 4020.28461 3427.2904 4020.32184 3427.44549 4020.29552 3427.6028 4020.20982 3427.73731 4020.07838 3427.82765 Z "},{"net":"GND","svgpath":"M 4003.9256 3437.1097 L 4003.63647 3437.07544 4003.46238 3437.01459 4003.3035 3436.92095 4003.16594 3436.79812 4003.05498 3436.65081 4002.9749 3436.48468 4002.92877 3436.30612 4002.9098 3436.0939 4002.9098 3420.0437 4002.87876 3419.78807 4002.79609 3419.57008 4002.65671 3419.36816 4002.53684 3419.24828 4002.39568 3419.14442 4002.1775 3419.04623 4001.88376 3418.9995 4000.3823 3418.9995 4000.09317 3418.96524 3999.91908 3418.90439 3999.7602 3418.81075 3999.62264 3418.68792 3999.51168 3418.54061 3999.4316 3418.37448 3999.38547 3418.19592 3999.3665 3417.9837 3999.3665 3402.71656 3999.40295 3401.48335 3999.50857 3400.28586 3999.68375 3399.09656 3999.92789 3397.91948 4000.24017 3396.75862 4000.61953 3395.61792 4001.06468 3394.50124 4001.47879 3393.61479 4001.56856 3393.4914 4001.69801 3393.4106 4001.84829 3393.38416 4001.99754 3393.41592 4002.12404 3393.50125 4003.04986 3394.42707 4003.11979 3394.52136 4003.22778 3394.72339 4003.42909 3394.9687 4004.92691 3396.46651 4005.0147 3396.59893 4005.04403 3396.75507 4005.01025 3396.91031 4004.91872 3397.04016 4004.78385 3397.12414 4004.42221 3397.25902 4003.9988 3397.49022 4003.6126 3397.77933 4003.27148 3398.12045 4002.98237 3398.50665 4002.75117 3398.93006 4002.58258 3399.38206 4002.48004 3399.85346 4002.4446 3400.34894 4002.4446 3404.65106 4002.48004 3405.14654 4002.58258 3405.61794 4002.75117 3406.06994 4002.98237 3406.49335 4003.27148 3406.87955 4003.6126 3407.22067 4003.9988 3407.50978 4004.42221 3407.74098 4004.87421 3407.90957 4005.34561 3408.01211 4005.67008 3408.03532 4005.8268 3408.08221 4005.98352 3408.03532 4006.30799 3408.01211 4006.77939 3407.90957 4007.23139 3407.74098 4007.6548 3407.50978 4008.041 3407.22067 4008.38212 3406.87955 4008.67123 3406.49335 4008.90243 3406.06994 4009.07102 3405.61794 4009.17356 3405.14654 4009.209 3404.65106 4009.209 3404.03641 4009.242 3403.87731 4009.33556 3403.74447 4009.47424 3403.65979 4009.63516 3403.63727 4009.79176 3403.6806 4009.9182 3403.78265 4010.07909 3403.9787 4011.29284 3405.19245 4011.37955 3405.32222 4011.41 3405.47529 4011.41 3408.47434 4011.37662 3408.63429 4011.28207 3408.76756 4011.14211 3408.85189 4010.89719 3408.93759 4010.70427 3409.05881 4010.54316 3409.21992 4010.42194 3409.41284 4010.33825 3409.65201 4010.25392 3409.79196 4010.12066 3409.88652 4009.9607 3409.91989 4009.80074 3409.88652 4009.66748 3409.79196 4009.58315 3409.65201 4009.49946 3409.41284 4009.37824 3409.21992 4009.21713 3409.05881 4009.02421 3408.93759 4008.80916 3408.86234 4008.56029 3408.8343 4006.01206 3408.8343 4005.8268 3408.78455 4005.64154 3408.8343 4005.43011 3408.8343 4005.18124 3408.86234 4004.96619 3408.93759 4004.77327 3409.05881 4004.61216 3409.21992 4004.49094 3409.41284 4004.41569 3409.62789 4004.38765 3409.87676 4004.38765 3413.23344 4004.41569 3413.48231 4004.49094 3413.69736 4004.61216 3413.89028 4004.77327 3414.05139 4004.96619 3414.17261 4005.18124 3414.24786 4005.43011 3414.2759 4005.6791 3414.2759 4005.83217 3414.30635 4005.96194 3414.39306 4006.04865 3414.52283 4006.0791 3414.6759 4006.0791 3416.67376 4006.04865 3416.82683 4005.96194 3416.9566 4005.89955 3417.01899 4005.67775 3417.33576 4005.51432 3417.68624 4005.41423 3418.05977 4005.38053 3418.445 4005.41423 3418.83023 4005.51432 3419.20376 4005.67775 3419.55424 4005.91721 3419.89623 4005.97104 3420.00537 4005.98955 3420.12566 4005.98955 3421.18035 4006.02239 3421.51383 4006.11393 3421.81559 4006.26259 3422.0937 4006.47516 3422.35273 4006.666 3422.54356 4006.7527 3422.67333 4006.78315 3422.8264 4006.7527 3422.97947 4006.666 3423.10924 4006.53623 3423.19595 4006.38315 3423.2264 4005.77758 3423.2264 4005.77758 3425.9649 4008.02395 3425.9649 4008.02395 3424.8672 4008.0544 3424.71412 4008.14111 3424.58435 4008.27088 3424.49765 4008.42395 3424.4672 4008.57702 3424.49765 4008.70679 3424.58435 4014.71737 3430.59494 4014.9764 3430.80751 4015.25451 3430.95617 4015.55627 3431.04771 4015.88975 3431.08055 4027.66025 3431.08055 4027.99373 3431.04771 4028.29549 3430.95617 4028.5736 3430.80751 4028.83263 3430.59494 4031.75484 3427.67273 4031.96741 3427.4137 4032.11607 3427.13559 4032.20761 3426.83383 4032.24045 3426.50035 4032.24045 3426.3649 4032.2709 3426.21183 4032.35761 3426.08206 4032.48738 3425.99535 4032.64045 3425.9649 4034.21483 3425.9649 4034.21483 3423.2264 4033.01091 3423.2264 4032.61298 3423.27226 4032.46854 3423.23438 4032.34806 3423.14616 4032.26832 3423.01991 4032.24045 3422.87321 4032.24045 3421.4429 4032.26386 3421.30806 4032.33135 3421.18901 4032.55755 3420.91362 4032.70617 3420.63559 4032.79771 3420.33383 4032.83055 3420.00035 4032.83055 3414.86645 4032.86393 3414.7065 4032.95848 3414.57323 4033.09844 3414.4889 4033.23716 3414.44036 4033.43008 3414.31914 4033.59119 3414.15803 4033.63771 3414.08399 4033.74861 3413.96801 4033.89616 3413.90494 4034.05664 3413.90494 4034.20419 3413.96801 4034.31509 3414.08399 4034.36161 3414.15803 4034.52272 3414.31914 4034.71564 3414.44036 4034.93069 3414.51561 4035.17956 3414.54365 4035.6945 3414.54365 4035.6945 3412.88783 4034.2157 3412.88783 4034.06263 3412.85738 4033.93286 3412.77067 4033.84615 3412.6409 4033.8157 3412.48783 4033.8157 3411.01617 4033.84615 3410.8631 4033.93286 3410.73333 4034.06263 3410.64662 4034.2157 3410.61617 4035.6945 3410.61617 4035.6945 3408.96035 4035.17956 3408.96035 4034.93069 3408.98839 4034.71564 3409.06364 4034.52272 3409.18486 4034.36161 3409.34597 4034.31509 3409.42001 4034.20419 3409.53599 4034.05664 3409.59906 4033.89616 3409.59906 4033.74861 3409.53599 4033.63771 3409.42001 4033.59119 3409.34597 4033.43008 3409.18486 4033.23716 3409.06364 4033.02211 3408.98839 4032.77324 3408.96035 4029.66856 3408.96035 4029.41969 3408.98839 4029.20464 3409.06364 4029.01172 3409.18486 4028.85061 3409.34597 4028.72939 3409.53889 4028.65414 3409.75394 4028.6403 3409.87679 4028.59819 3410.01559 4028.50935 3410.13026 4028.38546 3410.2057 4028.24282 3410.232 4027.93904 3410.232 4027.79639 3410.2057 4027.6725 3410.13026 4027.58366 3410.01559 4027.54155 3409.87679 4027.51351 3409.62789 4027.43826 3409.41284 4027.31704 3409.21992 4027.02109 3408.94403 4026.95624 3408.81695 4026.94011 3408.67518 4026.97477 3408.53678 4027.0558 3408.41934 4028.34337 3407.12629 4028.54212 3406.88358 4028.68251 3406.62094 4028.7689 3406.33616 4028.8 3406.02035 4028.8 3402.68529 4028.83045 3402.53222 4028.91716 3402.40245 4030.10816 3401.21145 4030.23793 3401.12474 4030.391 3401.09429 4030.54407 3401.12474 4030.67384 3401.21145 4030.76055 3401.34122 4030.791 3401.49429 4030.791 3404.65106 4030.82644 3405.14654 4030.92898 3405.61794 4031.09757 3406.06994 4031.32877 3406.49335 4031.61788 3406.87955 4031.959 3407.22067 4032.3452 3407.50978 4032.76861 3407.74098 4033.22061 3407.90957 4033.69201 3408.01211 4034.1732 3408.04653 4034.65439 3408.01211 4035.12579 3407.90957 4035.57779 3407.74098 4036.0012 3407.50978 4036.3874 3407.22067 4036.72852 3406.87955 4037.01763 3406.49335 4037.24883 3406.06994 4037.41742 3405.61794 4037.51996 3405.14654 4037.5554 3404.65106 4037.5554 3400.34894 4037.51996 3399.85346 4037.41742 3399.38206 4037.24883 3398.93006 4037.01763 3398.50665 4036.72852 3398.12045 4036.3874 3397.77933 4036.0012 3397.49022 4035.57779 3397.25902 4035.10517 3397.08595 4034.95049 3397.01531 4034.83913 3396.88679 4034.79122 3396.72363 4034.81542 3396.5553 4034.90736 3396.41225 4035.24306 3396.08079 4035.48632 3395.89089 4037.87596 3393.50125 4038.00246 3393.41592 4038.15171 3393.38416 4038.30199 3393.4106 4038.43144 3393.4914 4038.52121 3393.61479 4038.94457 3394.52263 4039.38845 3395.63981 4039.76652 3396.78095 4040.07749 3397.94216 4040.32031 3399.11951 4040.49414 3400.30901 4040.59841 3401.50661 4040.6335 3402.71656 4040.6335 3417.9837 4040.59924 3418.27283 4040.53839 3418.44692 4040.44475 3418.6058 4040.32192 3418.74336 4040.17461 3418.85432 4040.00848 3418.9344 4039.82992 3418.98053 4039.6177 3418.9995 4038.1344 3418.9995 4037.87877 3419.03054 4037.66078 3419.11321 4037.45886 3419.25259 4037.33898 3419.37246 4037.23512 3419.51362 4037.13693 3419.7318 4037.0902 3420.02554 4037.0902 3436.0939 4037.05594 3436.38303 4036.99509 3436.55712 4036.90145 3436.716 4036.77862 3436.85356 4036.63131 3436.96452 4036.46518 3437.0446 4036.28662 3437.09073 4036.0744 3437.1097 4003.9256 3437.1097 Z M 4006.98149 3433.1404 L 4007.23036 3433.11236 4007.44541 3433.03711 4007.63833 3432.91589 4007.79944 3432.75478 4007.92066 3432.56186 4007.99591 3432.34681 4008.02395 3432.09794 4008.02395 3430.4019 4005.77758 3430.4019 4005.77758 3433.1404 4006.98149 3433.1404 Z M 4034.21483 3433.1404 L 4034.21483 3430.4019 4031.96845 3430.4019 4031.96845 3432.09794 4031.99649 3432.34681 4032.07174 3432.56186 4032.19296 3432.75478 4032.35407 3432.91589 4032.54699 3433.03711 4032.76204 3433.11236 4033.01091 3433.1404 4034.21483 3433.1404 Z M 4038.28424 3414.54365 L 4038.53311 3414.51561 4038.74816 3414.44036 4038.94108 3414.31914 4039.10219 3414.15803 4039.22341 3413.96511 4039.29866 3413.75006 4039.3267 3413.50119 4039.3267 3412.88783 4037.7693 3412.88783 4037.7693 3414.54365 4038.28424 3414.54365 Z M 4037.7693 3408.96035 L 4037.7693 3410.61617 4039.3267 3410.61617 4039.3267 3410.00281 4039.29866 3409.75394 4039.22341 3409.53889 4039.10219 3409.34597 4038.94108 3409.18486 4038.74816 3409.06364 4038.53311 3408.98839 4038.28424 3408.96035 4037.7693 3408.96035 Z "}],"B":[{"net":"GND","svgpath":"M 4003.9256 3437.1097 L 4003.63647 3437.07544 4003.46238 3437.01459 4003.3035 3436.92095 4003.16594 3436.79812 4003.05498 3436.65081 4002.9749 3436.48468 4002.92877 3436.30612 4002.9098 3436.0939 4002.9098 3420.0437 4002.87876 3419.78807 4002.79609 3419.57008 4002.65671 3419.36816 4002.53684 3419.24828 4002.39568 3419.14442 4002.1775 3419.04623 4001.88376 3418.9995 4000.3823 3418.9995 4000.09317 3418.96524 3999.91908 3418.90439 3999.7602 3418.81075 3999.62264 3418.68792 3999.51168 3418.54061 3999.4316 3418.37448 3999.38547 3418.19592 3999.3665 3417.9837 3999.3665 3402.71656 3999.40295 3401.48335 3999.50857 3400.28586 3999.68375 3399.09656 3999.92789 3397.91948 4000.24017 3396.75862 4000.61953 3395.61792 4001.06468 3394.50124 4001.5741 3393.41239 4002.14608 3392.35505 4002.77866 3391.33281 4003.4697 3390.34915 4004.21685 3389.40741 4005.01758 3388.51078 4005.86918 3387.6623 4006.76874 3386.86486 4007.71321 3386.12116 4008.34766 3385.67838 4008.45659 3385.62482 4008.57658 3385.6064 4031.42342 3385.6064 4031.54341 3385.62482 4031.65234 3385.67838 4032.3055 3386.13505 4033.24914 3386.87982 4034.1478 3387.67827 4034.99843 3388.52771 4035.79815 3389.42525 4036.54424 3390.36783 4037.23417 3391.35227 4037.86559 3392.37522 4038.43637 3393.4332 4038.94457 3394.52263 4039.38845 3395.63981 4039.76652 3396.78095 4040.07749 3397.94216 4040.32031 3399.11951 4040.49414 3400.30901 4040.59841 3401.50661 4040.6335 3402.71656 4040.6335 3417.9837 4040.59924 3418.27283 4040.53839 3418.44692 4040.44475 3418.6058 4040.32192 3418.74336 4040.17461 3418.85432 4040.00848 3418.9344 4039.82992 3418.98053 4039.6177 3418.9995 4038.1344 3418.9995 4037.87877 3419.03054 4037.66078 3419.11321 4037.45886 3419.25259 4037.33898 3419.37246 4037.23512 3419.51362 4037.13693 3419.7318 4037.0902 3420.02554 4037.0902 3436.0939 4037.05594 3436.38303 4036.99509 3436.55712 4036.90145 3436.716 4036.77862 3436.85356 4036.63131 3436.96452 4036.46518 3437.0446 4036.28662 3437.09073 4036.0744 3437.1097 4003.9256 3437.1097 Z M 4029.44127 3432.6511 L 4029.64181 3432.64234 4029.83215 3432.61729 4030.01959 3432.57573 4030.20269 3432.518 4030.38006 3432.44453 4030.55036 3432.35588 4030.71228 3432.25272 4030.86459 3432.13585 4031.01258 3432.00024 4035.53024 3427.48258 4035.66585 3427.33459 4035.78272 3427.18228 4035.88588 3427.02036 4035.97453 3426.85006 4036.048 3426.67269 4036.10573 3426.48959 4036.14729 3426.30215 4036.17234 3426.11181 4036.1811 3425.91127 4036.1811 3407.57533 4036.20009 3407.45354 4036.25527 3407.34331 4036.3874 3407.22067 4036.72852 3406.87955 4037.01763 3406.49335 4037.24883 3406.06994 4037.41742 3405.61794 4037.51996 3405.14654 4037.5554 3404.65106 4037.5554 3400.34894 4037.51996 3399.85346 4037.41742 3399.38206 4037.24883 3398.93006 4037.01763 3398.50665 4036.72852 3398.12045 4036.3874 3397.77933 4036.0012 3397.49022 4035.57779 3397.25902 4035.12579 3397.09043 4034.65439 3396.98789 4034.1732 3396.95347 4033.69201 3396.98789 4033.22061 3397.09043 4032.76861 3397.25902 4032.3452 3397.49022 4031.959 3397.77933 4031.61788 3398.12045 4031.32877 3398.50665 4031.09757 3398.93006 4030.92898 3399.38206 4030.82644 3399.85346 4030.791 3400.34894 4030.791 3404.65106 4030.82644 3405.14654 4030.92898 3405.61794 4031.09757 3406.06994 4031.32877 3406.49335 4031.61788 3406.87955 4031.74845 3407.05319 4031.7789 3407.20626 4031.7789 3424.84259 4031.74845 3424.99566 4031.66174 3425.12543 4029.29525 3427.49192 4029.14922 3427.58495 4028.97755 3427.60755 4028.81241 3427.55549 4028.68475 3427.43851 4028.61849 3427.27854 4028.62604 3427.10555 4028.68087 3426.90093 4028.71457 3426.5157 4028.6971 3426.29853 4028.6971 3423.97697 4028.71457 3423.7598 4028.6971 3423.54263 4028.6971 3401.03113 4028.68834 3400.83059 4028.66329 3400.64025 4028.62173 3400.45281 4028.564 3400.26971 4028.49053 3400.09234 4028.40188 3399.92204 4028.29872 3399.76012 4028.18185 3399.60781 4028.04624 3399.45982 4024.02448 3395.43806 4023.87649 3395.30245 4023.72418 3395.18558 4023.56226 3395.08242 4023.39196 3394.99377 4023.21459 3394.9203 4023.03149 3394.86257 4022.84405 3394.82101 4022.65371 3394.79596 4022.45317 3394.7872 4011.34733 3394.7872 4011.14679 3394.79596 4010.95645 3394.82101 4010.76901 3394.86257 4010.58591 3394.9203 4010.40854 3394.99377 4010.23824 3395.08242 4010.07632 3395.18558 4009.92401 3395.30245 4009.77602 3395.43806 4007.9112 3397.30288 4007.76814 3397.39481 4007.59982 3397.41901 4007.43666 3397.3711 4007.23139 3397.25902 4006.77939 3397.09043 4006.30799 3396.98789 4005.8268 3396.95347 4005.34561 3396.98789 4004.87421 3397.09043 4004.42221 3397.25902 4003.9988 3397.49022 4003.6126 3397.77933 4003.27148 3398.12045 4002.98237 3398.50665 4002.75117 3398.93006 4002.58258 3399.38206 4002.48004 3399.85346 4002.4446 3400.34894 4002.4446 3404.65106 4002.48004 3405.14654 4002.58258 3405.61794 4002.75117 3406.06994 4002.98237 3406.49335 4003.27148 3406.87955 4003.6126 3407.22067 4003.9988 3407.50978 4004.42221 3407.74098 4004.87421 3407.90957 4005.34561 3408.01211 4005.8268 3408.04653 4006.30799 3408.01211 4006.77939 3407.90957 4007.23139 3407.74098 4007.6548 3407.50978 4008.041 3407.22067 4008.38212 3406.87955 4008.67123 3406.49335 4008.90243 3406.06994 4009.07102 3405.61794 4009.17356 3405.14654 4009.209 3404.65106 4009.209 3402.39641 4009.23945 3402.24334 4009.32616 3402.11357 4012.13317 3399.30656 4012.26294 3399.21985 4012.41601 3399.1894 4012.89411 3399.1894 4013.04488 3399.2189 4013.17341 3399.30305 4013.26074 3399.42944 4013.29399 3399.57943 4013.26826 3399.73088 4013.18733 3399.86147 4013.06316 3399.95192 4012.98526 3399.98825 4012.66849 3400.21005 4012.39505 3400.48349 4012.17325 3400.80026 4012.00982 3401.15074 4011.90973 3401.52427 4011.87603 3401.9095 4011.8939 3402.13124 4011.8939 3410.87179 4011.86345 3411.02486 4011.77674 3411.15463 4006.24053 3416.69084 4006.17299 3416.74555 4005.89955 3417.01899 4005.67775 3417.33576 4005.51432 3417.68624 4005.41423 3418.05977 4005.38053 3418.445 4005.41423 3418.83023 4005.51432 3419.20376 4005.67775 3419.55424 4005.89955 3419.87101 4006.17299 3420.14445 4006.48976 3420.36625 4006.84024 3420.52968 4007.21377 3420.62977 4007.599 3420.66347 4007.98423 3420.62977 4008.35776 3420.52968 4008.70824 3420.36625 4009.02501 3420.14445 4009.30741 3419.85821 4009.35222 3419.8048 4015.64524 3413.51178 4015.78085 3413.36379 4015.89772 3413.21148 4016.00088 3413.04956 4016.08953 3412.87926 4016.163 3412.70189 4016.22073 3412.51879 4016.26229 3412.33135 4016.28734 3412.14101 4016.2961 3411.94047 4016.2961 3406.19266 4016.32575 3406.04153 4016.41031 3405.9128 4016.53724 3405.82556 4016.68772 3405.79275 4016.83945 3405.81923 4016.96992 3405.90108 4017.09185 3406.01557 4017.55539 3406.35235 4018.05748 3406.62838 4018.59021 3406.8393 4019.14517 3406.98179 4019.71362 3407.05361 4020.28658 3407.05361 4020.85503 3406.98179 4021.40999 3406.8393 4021.94272 3406.62838 4022.44481 3406.35235 4022.90835 3406.01557 4023.32603 3405.62335 4023.58669 3405.30826 4023.7133 3405.20683 4023.86978 3405.16402 4024.0304 3405.18688 4024.16872 3405.27164 4024.262 3405.40437 4024.2949 3405.56323 4024.2949 3421.64709 4024.25742 3421.81614 4024.15202 3421.95351 4023.99843 3422.03346 4023.82544 3422.04101 4023.66547 3421.97475 4023.47124 3421.83875 4023.12076 3421.67532 4022.74723 3421.57523 4022.362 3421.54153 4021.97677 3421.57523 4021.60324 3421.67532 4021.25276 3421.83875 4020.93599 3422.06055 4020.66255 3422.33399 4020.44075 3422.65076 4020.27732 3423.00124 4020.17723 3423.37477 4020.14353 3423.76 4020.16084 3423.97541 4020.16014 3426.3088 4020.14353 3426.516 4020.16001 3426.72203 4020.15891 3430.43188 4020.19373 3430.8313 4020.29285 3431.20168 4020.45479 3431.54922 4020.67461 3431.86335 4020.94564 3432.13455 4021.25964 3432.35456 4021.60708 3432.5167 4021.9774 3432.61604 4022.37675 3432.6511 4029.44127 3432.6511 Z "}]},"pads":[{"layers":["F","B"],"pos":[4005.827,3402.5],"size":[4.7244,9.0551],"angle":0,"pin1":1,"shape":"oval","type":"th","drillsize":[2.559,6.8898],"drillshape":"oblong","holeCenterPoint":{"x":4005.8268,"y":3402.5},"polygon":[{"x":4005.7681,"y":3407.0269},{"x":4005.6505,"y":3407.021},{"x":4005.5333,"y":3407.0093},{"x":4005.4168,"y":3406.9917},{"x":4005.3014,"y":3406.9684},{"x":4005.1872,"y":3406.9393},{"x":4005.0747,"y":3406.9046},{"x":4004.964,"y":3406.8643},{"x":4004.8555,"y":3406.8186},{"x":4004.7493,"y":3406.7675},{"x":4004.6459,"y":3406.7111},{"x":4004.5454,"y":3406.6497},{"x":4004.4481,"y":3406.5834},{"x":4004.3542,"y":3406.5122},{"x":4004.264,"y":3406.4365},{"x":4004.1776,"y":3406.3564},{"x":4004.0954,"y":3406.2721},{"x":4004.0174,"y":3406.1838},{"x":4003.944,"y":3406.0917},{"x":4003.8753,"y":3405.9961},{"x":4003.8114,"y":3405.8971},{"x":4003.7525,"y":3405.7951},{"x":4003.6987,"y":3405.6903},{"x":4003.6503,"y":3405.583},{"x":4003.6073,"y":3405.4733},{"x":4003.5697,"y":3405.3617},{"x":4003.5378,"y":3405.2483},{"x":4003.5116,"y":3405.1335},{"x":4003.4912,"y":3405.0175},{"x":4003.4765,"y":3404.9006},{"x":4003.4677,"y":3404.7831},{"x":4003.4648,"y":3404.6654},{"x":4003.4648,"y":3400.3347},{"x":4003.4677,"y":3400.217},{"x":4003.4765,"y":3400.0995},{"x":4003.4912,"y":3399.9826},{"x":4003.5116,"y":3399.8666},{"x":4003.5378,"y":3399.7518},{"x":4003.5697,"y":3399.6384},{"x":4003.6073,"y":3399.5268},{"x":4003.6503,"y":3399.4171},{"x":4003.6987,"y":3399.3098},{"x":4003.7525,"y":3399.205},{"x":4003.8114,"y":3399.103},{"x":4003.8753,"y":3399.004},{"x":4003.944,"y":3398.9084},{"x":4004.0174,"y":3398.8163},{"x":4004.0954,"y":3398.728},{"x":4004.1776,"y":3398.6437},{"x":4004.264,"y":3398.5636},{"x":4004.3542,"y":3398.4879},{"x":4004.4481,"y":3398.4167},{"x":4004.5454,"y":3398.3504},{"x":4004.6459,"y":3398.289},{"x":4004.7493,"y":3398.2326},{"x":4004.8555,"y":3398.1815},{"x":4004.964,"y":3398.1358},{"x":4005.0747,"y":3398.0955},{"x":4005.1872,"y":3398.0608},{"x":4005.3014,"y":3398.0317},{"x":4005.4168,"y":3398.0084},{"x":4005.5333,"y":3397.9908},{"x":4005.6505,"y":3397.9791},{"x":4005.7681,"y":3397.9732},{"x":4005.8859,"y":3397.9732},{"x":4006.0035,"y":3397.9791},{"x":4006.1207,"y":3397.9908},{"x":4006.2372,"y":3398.0084},{"x":4006.3526,"y":3398.0317},{"x":4006.4668,"y":3398.0608},{"x":4006.5793,"y":3398.0955},{"x":4006.69,"y":3398.1358},{"x":4006.7985,"y":3398.1815},{"x":4006.9047,"y":3398.2326},{"x":4007.0081,"y":3398.289},{"x":4007.1086,"y":3398.3504},{"x":4007.2059,"y":3398.4167},{"x":4007.2998,"y":3398.4879},{"x":4007.39,"y":3398.5636},{"x":4007.4764,"y":3398.6437},{"x":4007.5586,"y":3398.728},{"x":4007.6366,"y":3398.8163},{"x":4007.71,"y":3398.9084},{"x":4007.7787,"y":3399.004},{"x":4007.8426,"y":3399.103},{"x":4007.9015,"y":3399.205},{"x":4007.9553,"y":3399.3098},{"x":4008.0037,"y":3399.4171},{"x":4008.0467,"y":3399.5268},{"x":4008.0843,"y":3399.6384},{"x":4008.1162,"y":3399.7518},{"x":4008.1424,"y":3399.8666},{"x":4008.1628,"y":3399.9826},{"x":4008.1775,"y":3400.0995},{"x":4008.1863,"y":3400.217},{"x":4008.1892,"y":3400.3347},{"x":4008.1892,"y":3404.6654},{"x":4008.1863,"y":3404.7831},{"x":4008.1775,"y":3404.9006},{"x":4008.1628,"y":3405.0175},{"x":4008.1424,"y":3405.1335},{"x":4008.1162,"y":3405.2483},{"x":4008.0843,"y":3405.3617},{"x":4008.0467,"y":3405.4733},{"x":4008.0037,"y":3405.583},{"x":4007.9553,"y":3405.6903},{"x":4007.9015,"y":3405.7951},{"x":4007.8426,"y":3405.8971},{"x":4007.7787,"y":3405.9961},{"x":4007.71,"y":3406.0917},{"x":4007.6366,"y":3406.1838},{"x":4007.5586,"y":3406.2721},{"x":4007.4764,"y":3406.3564},{"x":4007.39,"y":3406.4365},{"x":4007.2998,"y":3406.5122},{"x":4007.2059,"y":3406.5834},{"x":4007.1086,"y":3406.6497},{"x":4007.0081,"y":3406.7111},{"x":4006.9047,"y":3406.7675},{"x":4006.7985,"y":3406.8186},{"x":4006.69,"y":3406.8643},{"x":4006.5793,"y":3406.9046},{"x":4006.4668,"y":3406.9393},{"x":4006.3526,"y":3406.9684},{"x":4006.2372,"y":3406.9917},{"x":4006.1207,"y":3407.0093},{"x":4006.0035,"y":3407.021},{"x":4005.8859,"y":3407.0269}],"net":"M1"},{"layers":["F","B"],"pos":[4034.173,3402.5],"size":[4.7244,9.0551],"angle":0,"pin1":1,"shape":"oval","type":"th","drillsize":[2.559,6.8898],"drillshape":"oblong","holeCenterPoint":{"x":4034.1732,"y":3402.5},"polygon":[{"x":4034.1141,"y":3407.0269},{"x":4033.9965,"y":3407.021},{"x":4033.8793,"y":3407.0093},{"x":4033.7628,"y":3406.9917},{"x":4033.6474,"y":3406.9684},{"x":4033.5332,"y":3406.9393},{"x":4033.4207,"y":3406.9046},{"x":4033.31,"y":3406.8643},{"x":4033.2015,"y":3406.8186},{"x":4033.0953,"y":3406.7675},{"x":4032.9919,"y":3406.7111},{"x":4032.8914,"y":3406.6497},{"x":4032.7941,"y":3406.5834},{"x":4032.7002,"y":3406.5122},{"x":4032.61,"y":3406.4365},{"x":4032.5236,"y":3406.3564},{"x":4032.4414,"y":3406.2721},{"x":4032.3634,"y":3406.1838},{"x":4032.29,"y":3406.0917},{"x":4032.2213,"y":3405.9961},{"x":4032.1574,"y":3405.8971},{"x":4032.0985,"y":3405.7951},{"x":4032.0447,"y":3405.6903},{"x":4031.9963,"y":3405.583},{"x":4031.9533,"y":3405.4733},{"x":4031.9157,"y":3405.3617},{"x":4031.8838,"y":3405.2483},{"x":4031.8576,"y":3405.1335},{"x":4031.8372,"y":3405.0175},{"x":4031.8225,"y":3404.9006},{"x":4031.8137,"y":3404.7831},{"x":4031.8108,"y":3404.6654},{"x":4031.8108,"y":3400.3347},{"x":4031.8137,"y":3400.217},{"x":4031.8225,"y":3400.0995},{"x":4031.8372,"y":3399.9826},{"x":4031.8576,"y":3399.8666},{"x":4031.8838,"y":3399.7518},{"x":4031.9157,"y":3399.6384},{"x":4031.9533,"y":3399.5268},{"x":4031.9963,"y":3399.4171},{"x":4032.0447,"y":3399.3098},{"x":4032.0985,"y":3399.205},{"x":4032.1574,"y":3399.103},{"x":4032.2213,"y":3399.004},{"x":4032.29,"y":3398.9084},{"x":4032.3634,"y":3398.8163},{"x":4032.4414,"y":3398.728},{"x":4032.5236,"y":3398.6437},{"x":4032.61,"y":3398.5636},{"x":4032.7002,"y":3398.4879},{"x":4032.7941,"y":3398.4167},{"x":4032.8914,"y":3398.3504},{"x":4032.9919,"y":3398.289},{"x":4033.0953,"y":3398.2326},{"x":4033.2015,"y":3398.1815},{"x":4033.31,"y":3398.1358},{"x":4033.4207,"y":3398.0955},{"x":4033.5332,"y":3398.0608},{"x":4033.6474,"y":3398.0317},{"x":4033.7628,"y":3398.0084},{"x":4033.8793,"y":3397.9908},{"x":4033.9965,"y":3397.9791},{"x":4034.1141,"y":3397.9732},{"x":4034.2319,"y":3397.9732},{"x":4034.3495,"y":3397.9791},{"x":4034.4667,"y":3397.9908},{"x":4034.5832,"y":3398.0084},{"x":4034.6986,"y":3398.0317},{"x":4034.8128,"y":3398.0608},{"x":4034.9253,"y":3398.0955},{"x":4035.036,"y":3398.1358},{"x":4035.1445,"y":3398.1815},{"x":4035.2507,"y":3398.2326},{"x":4035.3541,"y":3398.289},{"x":4035.4546,"y":3398.3504},{"x":4035.5519,"y":3398.4167},{"x":4035.6458,"y":3398.4879},{"x":4035.736,"y":3398.5636},{"x":4035.8224,"y":3398.6437},{"x":4035.9046,"y":3398.728},{"x":4035.9826,"y":3398.8163},{"x":4036.056,"y":3398.9084},{"x":4036.1247,"y":3399.004},{"x":4036.1886,"y":3399.103},{"x":4036.2475,"y":3399.205},{"x":4036.3013,"y":3399.3098},{"x":4036.3497,"y":3399.4171},{"x":4036.3927,"y":3399.5268},{"x":4036.4303,"y":3399.6384},{"x":4036.4622,"y":3399.7518},{"x":4036.4884,"y":3399.8666},{"x":4036.5088,"y":3399.9826},{"x":4036.5235,"y":3400.0995},{"x":4036.5323,"y":3400.217},{"x":4036.5352,"y":3400.3347},{"x":4036.5352,"y":3404.6654},{"x":4036.5323,"y":3404.7831},{"x":4036.5235,"y":3404.9006},{"x":4036.5088,"y":3405.0175},{"x":4036.4884,"y":3405.1335},{"x":4036.4622,"y":3405.2483},{"x":4036.4303,"y":3405.3617},{"x":4036.3927,"y":3405.4733},{"x":4036.3497,"y":3405.583},{"x":4036.3013,"y":3405.6903},{"x":4036.2475,"y":3405.7951},{"x":4036.1886,"y":3405.8971},{"x":4036.1247,"y":3405.9961},{"x":4036.056,"y":3406.0917},{"x":4035.9826,"y":3406.1838},{"x":4035.9046,"y":3406.2721},{"x":4035.8224,"y":3406.3564},{"x":4035.736,"y":3406.4365},{"x":4035.6458,"y":3406.5122},{"x":4035.5519,"y":3406.5834},{"x":4035.4546,"y":3406.6497},{"x":4035.3541,"y":3406.7111},{"x":4035.2507,"y":3406.7675},{"x":4035.1445,"y":3406.8186},{"x":4035.036,"y":3406.8643},{"x":4034.9253,"y":3406.9046},{"x":4034.8128,"y":3406.9393},{"x":4034.6986,"y":3406.9684},{"x":4034.5832,"y":3406.9917},{"x":4034.4667,"y":3407.0093},{"x":4034.3495,"y":3407.021},{"x":4034.2319,"y":3407.0269}],"net":"M2"},{"layers":["F","B"],"pos":[4020,3402.5],"size":[7.0866,7.0866],"angle":0,"shape":"oval","type":"th","drillsize":[5.5118,5.5118],"holeCenterPoint":{"x":4020.0001,"y":3402.5001},"polygon":[],"net":"0"}],"nets":["0","M1","M2","VCC","GND","EB","EA"],"bom":{"both":[[1,"100nF","C0603",[["C1",0]],["SAMSUNG","CL10B104KB8NNNC","LCSC","C1591"]],[2,"10K","R0603",[["R1",1],["R2",2]],["YAGEO","RC0603JR-0710KL","LCSC","C99198"]],[1,"THD0515-08CL-SN","FPC-SMD_8P-P0.50_THD0515-08CL-SN",[["CONN",3]],["THD","THD0515-08CL-SN","LCSC","C283141"]],[2,"TLE49646MXTMA1","SOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR",[["U1",4],["U2",5]],["Infineon","TLE49646MXTMA1","LCSC","C126660"]]],"F":[[1,"100nF","C0603",[["C1",0]],["SAMSUNG","CL10B104KB8NNNC","LCSC","C1591"]],[2,"10K","R0603",[["R1",1],["R2",2]],["YAGEO","RC0603JR-0710KL","LCSC","C99198"]],[1,"THD0515-08CL-SN","FPC-SMD_8P-P0.50_THD0515-08CL-SN",[["CONN",3]],["THD","THD0515-08CL-SN","LCSC","C283141"]],[2,"TLE49646MXTMA1","SOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR",[["U1",4],["U2",5]],["Infineon","TLE49646MXTMA1","LCSC","C126660"]]],"B":[],"skipped":[],"customColumns":["BOM_Manufacturer","BOM_Manufacturer Part","BOM_Supplier","BOM_Supplier Part"]}};
///////////////////////////////////////////////

///////////////////////////////////////////////
/* Utility functions */

var storagePrefix = 'ibom__' + pcbdata.metadata.title + '__' +
  pcbdata.metadata.revision + '__#';

function buildKeyForLocalStorageDict() {  
  return storagePrefix;
}

var storage;
var _isLocalStorageUsed = false;

function initStorage(key) {
  try {
    window.localStorage.getItem("blank");
    storage = window.localStorage;    
    _isLocalStorageUsed = true;
  } catch (e) {
    // localStorage not available
  }
  if (!storage) {
    try {
      window.sessionStorage.getItem("blank");
      storage = window.sessionStorage;      
    } catch (e) {
      // sessionStorage also not available
    }
  }
}



function readStorage(key) {
  if(!storage) {
    return null;
  }

  if(!_isLocalStorageUsed) {    
    return storage.getItem(storagePrefix + key);
  }

  try {
    var dictKey = buildKeyForLocalStorageDict();
    var obj = JSON.parse(storage.getItem(dictKey));    
    if(!obj || !obj.hasOwnProperty(key)) {
      return null;
    }

    return JSON.parse(storage.getItem(dictKey))[key];
  } catch(e) {
    console.log('[ibom]: Unable to obtain a value from local storage!');
    console.log(e);
  }
  
  // Original implementation
  // return storage.getItem(storagePrefix + key);
}


function writeStorage(key, value) {
  if(!storage) {
    return;
  }

  if(!_isLocalStorageUsed) {
    storage.setItem(storagePrefix + key, value);
    return;
  }

  try {
    var dictKey = buildKeyForLocalStorageDict();
    if(!storage.getItem(dictKey)) {
      storage.setItem(dictKey,JSON.stringify({
        _storagePrefix: storagePrefix
      }));
    }

    var obj = JSON.parse(storage.getItem(dictKey));
    obj[key] = value;
    storage.setItem(dictKey,JSON.stringify(obj));            
  } catch(e) {
    console.log('[ibom]: Unable to write key to local storage!');
    console.log(e);
  }
  
  // Original implementation
  // storage.setItem(storagePrefix + key, value);
}

function fancyDblClickHandler(el, onsingle, ondouble) {
  return function() {
    if (el.getAttribute("data-dblclick") == null) {
      el.setAttribute("data-dblclick", 1);
      setTimeout(function() {
        if (el.getAttribute("data-dblclick") == 1) {
          onsingle();
        }
        el.removeAttribute("data-dblclick");
      }, 200);
    } else {
      el.removeAttribute("data-dblclick");
      ondouble();
    }
  }
}

function smoothScrollToRow(rowid) {
  document.getElementById(rowid).scrollIntoView({
    behavior: "smooth",
    block: "center",
    inline: "nearest"
  });
}

function focusInputField(input) {
  input.scrollIntoView(false);
  input.focus();
  input.select();
}

function copyToClipboard() {
  var text = '';
  for (var node of bomhead.childNodes[0].childNodes) {
    if (node.firstChild) {
      text = text + node.firstChild.nodeValue;
    }
    if (node != bomhead.childNodes[0].lastChild) {
      text += '\t';
    }
  }
  text += '\n';
  for (var row of bombody.childNodes) {
    for (var cell of row.childNodes) {
      for (var node of cell.childNodes) {
        if (node.nodeName == "INPUT") {
          if (node.checked) {
            text = text + '✓';
          }
        } else if (node.nodeName == "MARK") {
          text = text + node.firstChild.nodeValue;
        } else {
          text = text + node.nodeValue;
        }
      }
      if (cell != row.lastChild) {
        text += '\t';
      }
    }
    text += '\n';
  }
  var textArea = document.createElement("textarea");
  textArea.classList.add('clipboard-temp');
  textArea.value = text;

  document.body.appendChild(textArea);
  textArea.focus();
  textArea.select();

  try {
    if (document.execCommand('copy')) {
      console.log('Bom copied to clipboard.');
    }
  } catch (err) {
    console.log('Can not copy to clipboard.');
  }

  document.body.removeChild(textArea);
}

function removeGutterNode(node) {
  for (var i = 0; i < node.childNodes.length; i++) {
    if (node.childNodes[i].classList &&
      node.childNodes[i].classList.contains("gutter")) {
      node.removeChild(node.childNodes[i]);
      break;
    }
  }
}

function cleanGutters() {
  removeGutterNode(document.getElementById("bot"));
  removeGutterNode(document.getElementById("canvasdiv"));
}

var units = {
  prefixes: {
    giga: ["G", "g", "giga", "Giga", "GIGA"],
    mega: ["M", "mega", "Mega", "MEGA"],
    kilo: ["K", "k", "kilo", "Kilo", "KILO"],
    milli: ["m", "milli", "Milli", "MILLI"],
    micro: ["U", "u", "micro", "Micro", "MICRO", "μ", "µ"], // different utf8 μ
    nano: ["N", "n", "nano", "Nano", "NANO"],
    pico: ["P", "p", "pico", "Pico", "PICO"],
  },
  unitsShort: ["R", "r", "Ω", "F", "f", "H", "h"],
  unitsLong: [
    "OHM", "Ohm", "ohm", "ohms",
    "FARAD", "Farad", "farad",
    "HENRY", "Henry", "henry"
  ],
  getMultiplier: function(s) {
    if (this.prefixes.giga.includes(s)) return 1e9;
    if (this.prefixes.mega.includes(s)) return 1e6;
    if (this.prefixes.kilo.includes(s)) return 1e3;
    if (this.prefixes.milli.includes(s)) return 1e-3;
    if (this.prefixes.micro.includes(s)) return 1e-6;
    if (this.prefixes.nano.includes(s)) return 1e-9;
    if (this.prefixes.pico.includes(s)) return 1e-12;
    return 1;
  },
  valueRegex: null,
}

function initUtils() {
  var allPrefixes = units.prefixes.giga
                    .concat(units.prefixes.mega)
                    .concat(units.prefixes.kilo)
                    .concat(units.prefixes.milli)
                    .concat(units.prefixes.micro)
                    .concat(units.prefixes.nano)
                    .concat(units.prefixes.pico);
  var allUnits = units.unitsShort.concat(units.unitsLong);
  units.valueRegex = new RegExp("^([0-9\.]+)" +
                         "\\s*(" + allPrefixes.join("|") + ")?" +
                         "(" + allUnits.join("|") + ")?" +
                         "(\\b.*)?$", "");
  units.valueAltRegex = new RegExp("^([0-9]*)" +
                         "(" + units.unitsShort.join("|") + ")?" +
                         "([GgMmKkUuNnPp])?" +
                         "([0-9]*)" +
                         "(\\b.*)?$", "");
  for (var bom_type of ["both", "F", "B"]) {
    for (var row of pcbdata.bom[bom_type]) {
      row.push(parseValue(row[1], row[3][0][0]));
    }
  }
}

function parseValue(val, ref) {
  var inferUnit = (unit, ref) => {
    if (unit) {
      unit = unit.toLowerCase();
      if (unit == 'Ω' || unit == "ohm" || unit == "ohms") {
        unit = 'r';
      }
      unit = unit[0];
    } else {
      ref = /^([a-z]+)\d+$/i.exec(ref);
      if (ref) {
        ref = ref[1].toLowerCase();
        if (ref == "c") unit = 'f';
        else if (ref == "l") unit = 'h';
        else if (ref == "r" || ref == "rv") unit = 'r';
        else unit = null;
      }
    }
    return unit;
  };
  val = val.replace(/,/g, "");
  var match = units.valueRegex.exec(val);
  var unit;
  if (match) {
    val = parseFloat(match[1]);
    if (match[2]) {
      val = val * units.getMultiplier(match[2]);
    }
    unit = inferUnit(match[3], ref);
    if (!unit) return null;
    else return {
      val: val,
      unit: unit,
      extra: match[4],
    }
  }
  match = units.valueAltRegex.exec(val);
  if (match && (match[1] || match[4])) {
    val = parseFloat(match[1] + "." + match[4]);
    if (match[3]) {
      val = val * units.getMultiplier(match[3]);
    }
    unit = inferUnit(match[2], ref);
    if (!unit) return null;
    else return {
      val: val,
      unit: unit,
      extra: match[5],
    }
  }
  return null;
}

function valueCompare(a, b, stra, strb) {
  if (a === null && b === null) {
    // Failed to parse both values, compare them as strings.
    if (stra != strb) return stra > strb ? 1 : -1;
    else return 0;
  } else if (a === null) {
    return 1;
  } else if (b === null) {
    return -1;
  } else {
    if (a.unit != b.unit) return a.unit > b.unit ? 1 : -1;
    else if (a.val != b.val) return a.val > b.val ? 1 : -1;
    else if (a.extra != b.extra) return a.extra > b.extra ? 1 : -1;
    else return 0;
  }
}

function validateSaveImgDimension(element) {
  var valid = false;
  var intValue = 0;
  if (/^[1-9]\d*$/.test(element.value)) {
    intValue = parseInt(element.value);
    if (intValue <= 16000) {
      valid = true;
    }
  }
  if (valid) {
    element.classList.remove("invalid");
  } else {
    element.classList.add("invalid");
  }
  return intValue;
}

function saveImage(layer) {
  var width = validateSaveImgDimension(document.getElementById("render-save-width"));
  var height = validateSaveImgDimension(document.getElementById("render-save-height"));
  var bgcolor = null;
  if (!document.getElementById("render-save-transparent").checked) {
    var style = getComputedStyle(topmostdiv);
    bgcolor = style.getPropertyValue("background-color");
  }
  if (!width || !height) return;

  // Prepare image
  var canvas = document.createElement("canvas");
  var layerdict = {
    transform: {
      x: 0,
      y: 0,
      s: 1,
      panx: 0,
      pany: 0,
      zoom: 1,
    },
    bg: canvas,
    fab: canvas,
    silk: canvas,
    highlight: canvas,
    layer: layer,
  }
  // Do the rendering
  recalcLayerScale(layerdict, width, height);
  prepareLayer(layerdict);
  clearCanvas(canvas, bgcolor);
  drawBackground(layerdict, false);
  drawHighlightsOnLayer(layerdict, false);

  // Save image
  var imgdata = canvas.toDataURL("image/png");

  var filename = pcbdata.metadata.title;
  if (pcbdata.metadata.revision) {
    filename += `.${pcbdata.metadata.revision}`;
  }
  filename += `.${layer}.png`;
  saveFile(filename, dataURLtoBlob(imgdata));
}

function saveSettings() {
  var data = {
    type: "InteractiveHtmlBom settings",
    version: 1,
    pcbmetadata: pcbdata.metadata,
    settings: settings,
  }
  var blob = new Blob([JSON.stringify(data, null, 4)], {type: "application/json"});
  saveFile(`${pcbdata.metadata.title}.settings.json`, blob);
}

function loadSettings() {
  var input = document.createElement("input");
  input.type = "file";
  input.accept = ".settings.json";
  input.onchange = function(e) {
    var file = e.target.files[0];
    var reader = new FileReader();
    reader.onload = readerEvent => {
      var content = readerEvent.target.result;
      var newSettings;
      try {
        newSettings = JSON.parse(content);
      } catch(e) {
        alert("Selected file is not InteractiveHtmlBom settings file.");
        return;
      }
      if (newSettings.type != "InteractiveHtmlBom settings") {
        alert("Selected file is not InteractiveHtmlBom settings file.");
        return;
      }
      var metadataMatches = newSettings.hasOwnProperty("pcbmetadata");
      if (metadataMatches) {
        for (var k in pcbdata.metadata) {
          if (!newSettings.pcbmetadata.hasOwnProperty(k) || newSettings.pcbmetadata[k] != pcbdata.metadata[k]) {
            metadataMatches = false;
          }
        }
      }
      if (!metadataMatches) {
        var currentMetadata = JSON.stringify(pcbdata.metadata, null, 4);
        var fileMetadata = JSON.stringify(newSettings.pcbmetadata, null, 4);
        if (!confirm(
          `Settins file metadata does not match current metadata.\n\n` +
          `Page metadata:\n${currentMetadata}\n\n` +
          `Settings file metadata:\n${fileMetadata}\n\n` +
          `Press OK if you would like to import settings anyway.`)) {
          return;
        }
      }
      overwriteSettings(newSettings.settings);
    }
    reader.readAsText(file, 'UTF-8');
  }
  input.click();
}

function overwriteSettings(newSettings) {

  initDone = false;
  Object.assign(settings, newSettings);
  writeStorage("bomlayout", settings.bomlayout);
  writeStorage("bommode", settings.bommode);
  writeStorage("canvaslayout", settings.canvaslayout);
  writeStorage("bomCheckboxes", settings.checkboxes.join(","));
  document.getElementById("bomCheckboxes").value = settings.checkboxes.join(",");
  for (var checkbox of settings.checkboxes) {
    writeStorage("checkbox_" + checkbox, settings.checkboxStoredRefs[checkbox]);
  }
  writeStorage("darkenWhenChecked", settings.darkenWhenChecked);
  padsVisible(settings.renderPads);
  document.getElementById("padsCheckbox").checked = settings.renderPads;
  fabricationVisible(settings.renderFabrication);
  document.getElementById("fabricationCheckbox").checked = settings.renderFabrication;
  silkscreenVisible(settings.renderSilkscreen);
  document.getElementById("silkscreenCheckbox").checked = settings.renderSilkscreen;
  referencesVisible(settings.renderReferences);
  document.getElementById("referencesCheckbox").checked = settings.renderReferences;
  valuesVisible(settings.renderValues);
  document.getElementById("valuesCheckbox").checked = settings.renderValues;
  tracksVisible(settings.renderTracks);
  document.getElementById("tracksCheckbox").checked = settings.renderTracks;
  zonesVisible(settings.renderZones);
  document.getElementById("zonesCheckbox").checked = settings.renderZones;
  dnpOutline(settings.renderDnpOutline);
  document.getElementById("dnpOutlineCheckbox").checked = settings.renderDnpOutline;
  setRedrawOnDrag(settings.redrawOnDrag);
  document.getElementById("dragCheckbox").checked = settings.redrawOnDrag;
  setShowCrosshair(settings.showCrosshair);
  document.getElementById("crosshairCheckbox").checked = settings.showCrosshair;
  setDarkMode(settings.darkMode);
  document.getElementById("darkmodeCheckbox").checked = settings.darkMode;
  setHighlightPin1(settings.highlightpin1);
  document.getElementById("highlightpin1Checkbox").checked = settings.highlightpin1;
  writeStorage("boardRotation", settings.boardRotation);
  document.getElementById("boardRotation").value = settings.boardRotation / 5;
  document.getElementById("rotationDegree").textContent = settings.boardRotation;
  initDone = true;
  prepCheckboxes();
  changeBomLayout(settings.bomlayout);
}

function saveFile(filename, blob) {
  var link = document.createElement("a");
  var objurl = URL.createObjectURL(blob);
  link.download = filename;
  link.href = objurl;
  link.click();
}

function dataURLtoBlob(dataurl) {
  var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
      bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
  while(n--){
      u8arr[n] = bstr.charCodeAt(n);
  }
  return new Blob([u8arr], {type:mime});
}

var settings = {
  canvaslayout: "default",
  bomlayout: "default",
  bommode: "grouped",
  checkboxes: [],
  checkboxStoredRefs: {},
  darkMode: false,
  highlightpin1: false,
  redrawOnDrag: true,
  boardRotation: 0,
  renderPads: true,
  renderReferences: true,
  renderValues: true,
  renderSilkscreen: true,
  renderFabrication: true,
  renderDnpOutline: false,
  renderTracks: true,
  renderZones: true,
}

function initDefaults() {
  settings.bomlayout = readStorage("bomlayout");
  if (settings.bomlayout === null) {
    settings.bomlayout = config.bom_view;
  }
  if (!['bom-only', 'left-right', 'top-bottom'].includes(settings.bomlayout)) {
    settings.bomlayout = config.bom_view;
  }
  settings.bommode = readStorage("bommode");
  if (settings.bommode === null) {
    settings.bommode = "grouped";
  }
  if (!["grouped", "ungrouped", "netlist"].includes(settings.bommode)) {
    settings.bommode = "grouped";
  }
  settings.canvaslayout = readStorage("canvaslayout");
  if (settings.canvaslayout === null) {
    settings.canvaslayout = config.layer_view;
  }
  var bomCheckboxes = readStorage("bomCheckboxes");
  if (bomCheckboxes === null) {
    bomCheckboxes = config.checkboxes;
  }
  settings.checkboxes = bomCheckboxes.split(",").filter((e) => e);
  document.getElementById("bomCheckboxes").value = bomCheckboxes;

  settings.darkenWhenChecked = readStorage("darkenWhenChecked") || "";
  populateDarkenWhenCheckedOptions();

  function initBooleanSetting(storageString, def, elementId, func) {    
    var b = readStorage(storageString);
    if (b === null) {
      b = def;
    } else {
      b = (b === true);
    }
    document.getElementById(elementId).checked = b;
    func(b);
  }

  initBooleanSetting("padsVisible", config.show_pads, "padsCheckbox", padsVisible);
  initBooleanSetting("fabricationVisible", config.show_fabrication, "fabricationCheckbox", fabricationVisible);
  initBooleanSetting("silkscreenVisible", config.show_silkscreen, "silkscreenCheckbox", silkscreenVisible);
  initBooleanSetting("referencesVisible", true, "referencesCheckbox", referencesVisible);
  initBooleanSetting("valuesVisible", false, "valuesCheckbox", valuesVisible);
  if ("tracks" in pcbdata) {
    initBooleanSetting("tracksVisible", true, "tracksCheckbox", tracksVisible);
    initBooleanSetting("zonesVisible", true, "zonesCheckbox", zonesVisible);
  } else {
    document.getElementById("tracksAndZonesCheckboxes").style.display = "none";
    tracksVisible(false);
    zonesVisible(false);
  }
  initBooleanSetting("dnpOutline", false, "dnpOutlineCheckbox", dnpOutline);
  initBooleanSetting("redrawOnDrag", config.redraw_on_drag, "dragCheckbox", setRedrawOnDrag);
  initBooleanSetting("showCrosshair", config.show_crosshair, "crosshairCheckbox", setShowCrosshair);
  initBooleanSetting("darkmode", config.dark_mode, "darkmodeCheckbox", setDarkMode);
  initBooleanSetting("highlightpin1", config.highlight_pin1, "highlightpin1Checkbox", setHighlightPin1);
  settings.boardRotation = readStorage("boardRotation");
  if (settings.boardRotation === null) {
    settings.boardRotation = config.board_rotation * 5;
  } else {
    settings.boardRotation = parseInt(settings.boardRotation);
  }
  document.getElementById("boardRotation").value = settings.boardRotation / 5;
  document.getElementById("rotationDegree").textContent = settings.boardRotation;
}

// Helper classes for user js callbacks.

const IBOM_EVENT_TYPES = {
  ALL: "all",
  HIGHLIGHT_EVENT: "highlightEvent",
  CHECKBOX_CHANGE_EVENT: "checkboxChangeEvent",
  BOM_BODY_CHANGE_EVENT: "bomBodyChangeEvent",
}

const EventHandler = {
  callbacks: {},
  init: function() {
    for (eventType of Object.values(IBOM_EVENT_TYPES))
      this.callbacks[eventType] = [];
  },
  registerCallback: function(eventType, callback) {
    this.callbacks[eventType].push(callback);
  },
  emitEvent: function(eventType, eventArgs) {
    event = {
      eventType: eventType,
      args: eventArgs,
    }
    var callback;
    for(callback of this.callbacks[eventType])
      callback(event);
    for(callback of this.callbacks[IBOM_EVENT_TYPES.ALL])
      callback(event);
  }
}
EventHandler.init();

///////////////////////////////////////////////

///////////////////////////////////////////////
/* PCB rendering code */

var emptyContext2d = document.createElement("canvas").getContext("2d");
var hitTestContext2d = document.createElement("canvas").getContext("2d");

function deg2rad(deg) {
  return deg * Math.PI / 180;
}

function calcFontPoint(linepoint, text, offsetx, offsety, tilt) {
  var point = [
    linepoint[0] * text.width + offsetx,
    linepoint[1] * text.height + offsety
  ];
  // This approximates pcbnew behavior with how text tilts depending on horizontal justification
  point[0] -= (linepoint[1] + 0.5 * (1 + text.justify[0])) * text.height * tilt;
  return point;
}

function drawText(ctx, text, color) {
  if ("ref" in text && !settings.renderReferences) return;
  if ("val" in text && !settings.renderValues) return;
  ctx.save();
  ctx.fillStyle = color;
  ctx.strokeStyle = color;
  ctx.lineCap = "round";
  ctx.lineJoin = "round";
  ctx.lineWidth = text.thickness;

  if ("svgpath" in text) {    
    // TODO: This path must be cached!
    const path = new Path2D(text.svgpath);
    if(text.useTrueTypeFontRendering) {            
      ctx.fill(path);
    } else {
      ctx.stroke(path);
    }
    
    ctx.restore();
    return;
  }

  ctx.translate(...text.pos);
  ctx.translate(text.thickness * 0.5, 0);
  var angle = -text.angle;
  if (text.attr.includes("mirrored")) {
    ctx.scale(-1, 1);
    angle = -angle;
  }
  var tilt = 0;
  if (text.attr.includes("italic")) {
    tilt = 0.125;
  }
  var interline = text.height * 1.5 + text.thickness;
  var txt = text.text.split("\n");
  // KiCad ignores last empty line.
  if (txt[txt.length - 1] == '') txt.pop();
  ctx.rotate(deg2rad(angle));
  var offsety = (1 - text.justify[1]) / 2 * text.height; // One line offset
  offsety -= (txt.length - 1) * (text.justify[1] + 1) / 2 * interline; // Multiline offset
  for (var i in txt) {
    var lineWidth = text.thickness + interline / 2 * tilt;
    for (var j = 0; j < txt[i].length; j++) {
      if (txt[i][j] == '\t') {
        var fourSpaces = 4 * pcbdata.font_data[' '].w * text.width;
        lineWidth += fourSpaces - lineWidth % fourSpaces;
      } else {
        if (txt[i][j] == '~') {
          j++;
          if (j == txt[i].length)
            break;
        }
        lineWidth += pcbdata.font_data[txt[i][j]].w * text.width;
      }
    }
    var offsetx = -lineWidth * (text.justify[0] + 1) / 2;
    var inOverbar = false;
    for (var j = 0; j < txt[i].length; j++) {
      if (txt[i][j] == '\t') {
        var fourSpaces = 4 * pcbdata.font_data[' '].w * text.width;
        offsetx += fourSpaces - offsetx % fourSpaces;
        continue;
      } else if (txt[i][j] == '~') {
        j++;
        if (j == txt[i].length)
          break;
        if (txt[i][j] != '~') {
          inOverbar = !inOverbar;
        }
      }
      var glyph = pcbdata.font_data[txt[i][j]];
      if (inOverbar) {
        var overbarStart = [offsetx, -text.height * 1.4 + offsety];
        var overbarEnd = [offsetx + text.width * glyph.w, overbarStart[1]];

        if (!lastHadOverbar) {
          overbarStart[0] += text.height * 1.4 * tilt;
          lastHadOverbar = true;
        }
        ctx.beginPath();
        ctx.moveTo(...overbarStart);
        ctx.lineTo(...overbarEnd);
        ctx.stroke();
      } else {
        lastHadOverbar = false;
      }
      for (var line of glyph.l) {
        ctx.beginPath();
        ctx.moveTo(...calcFontPoint(line[0], text, offsetx, offsety, tilt));
        for (var k = 1; k < line.length; k++) {
          ctx.lineTo(...calcFontPoint(line[k], text, offsetx, offsety, tilt));
        }
        ctx.stroke();
      }
      offsetx += glyph.w * text.width;
    }
    offsety += interline;
  }
  ctx.restore();
}

function drawedge(ctx, scalefactor, edge, color) {
  ctx.strokeStyle = color;
  ctx.lineWidth = Math.max(1 / scalefactor, edge.width);
  ctx.lineCap = "round";
  if ("svgpath" in edge) {
    ctx.stroke(new Path2D(edge.svgpath));
  } else {
    ctx.beginPath();
    if (edge.type == "segment") {
      ctx.moveTo(...edge.start);
      ctx.lineTo(...edge.end);
    }
    if (edge.type == "rect") {
      ctx.moveTo(...edge.start);
      ctx.lineTo(edge.start[0], edge.end[1]);
      ctx.lineTo(...edge.end);
      ctx.lineTo(edge.end[0], edge.start[1]);
      ctx.lineTo(...edge.start);
    }
    if (edge.type == "arc") {
      ctx.arc(
        ...edge.start,
        edge.radius,
        deg2rad(edge.startangle),
        deg2rad(edge.endangle));
    }
    if (edge.type == "circle") {
      ctx.arc(
        ...edge.start,
        edge.radius,
        0, 2 * Math.PI);
      ctx.closePath();
    }
    if (edge.type == "curve") {
      ctx.moveTo(...edge.start);
      ctx.bezierCurveTo(...edge.cpa, ...edge.cpb, ...edge.end);
    }
    ctx.stroke();
  }
}

function getChamferedRectPath(size, radius, chamfpos, chamfratio) {
  // chamfpos is a bitmask, left = 1, right = 2, bottom left = 4, bottom right = 8
  var path = new Path2D();
  var width = size[0];
  var height = size[1];
  var x = width * -0.5;
  var y = height * -0.5;
  var chamfOffset = Math.min(width, height) * chamfratio;
  path.moveTo(x, 0);
  if (chamfpos & 4) {
    path.lineTo(x, y + height - chamfOffset);
    path.lineTo(x + chamfOffset, y + height);
    path.lineTo(0, y + height);
  } else {
    path.arcTo(x, y + height, x + width, y + height, radius);
  }
  if (chamfpos & 8) {
    path.lineTo(x + width - chamfOffset, y + height);
    path.lineTo(x + width, y + height - chamfOffset);
    path.lineTo(x + width, 0);
  } else {
    path.arcTo(x + width, y + height, x + width, y, radius);
  }
  if (chamfpos & 2) {
    path.lineTo(x + width, y + chamfOffset);
    path.lineTo(x + width - chamfOffset, y);
    path.lineTo(0, y);
  } else {
    path.arcTo(x + width, y, x, y, radius);
  }
  if (chamfpos & 1) {
    path.lineTo(x + chamfOffset, y);
    path.lineTo(x, y + chamfOffset);
    path.lineTo(x, 0);
  } else {
    path.arcTo(x, y, x, y + height, radius);
  }
  path.closePath();
  return path;
}

function getOblongPath(size) {
  return getChamferedRectPath(size, Math.min(size[0], size[1]) / 2, 0, 0);
}

function getPolygonsPath(shape) {
  if (shape.path2d) {
    return shape.path2d;
  }
  if ("svgpath" in shape) {
    shape.path2d = new Path2D(shape.svgpath);
  } else {
    var path = new Path2D();
    for (var polygon of shape.polygons) {
      path.moveTo(...polygon[0]);
      for (var i = 1; i < polygon.length; i++) {
        path.lineTo(...polygon[i]);
      }
      path.closePath();
    }
    shape.path2d = path;
  }
  return shape.path2d;
}

function drawPolygonShape(ctx, shape, color) {
  ctx.save();
  ctx.fillStyle = color;
  if (!("svgpath" in shape)) {
    ctx.translate(...shape.pos);
    ctx.rotate(deg2rad(-shape.angle));
  }
  ctx.fill(getPolygonsPath(shape));
  ctx.restore();
}


function drawPolylineShape(ctx, shape, color) {
  ctx.save();
  ctx.strokeStyle = color;
  ctx.lineWidth = shape.width;
  if (!("svgpath" in shape)) {
    ctx.translate(...shape.pos);
    ctx.rotate(deg2rad(-shape.angle));
  }
  ctx.stroke(getPolygonsPath(shape));
  ctx.restore();
}

function drawDrawing(ctx, scalefactor, drawing, color) {  
  if (["segment", "arc", "circle", "curve"].includes(drawing.type)) {
    drawedge(ctx, scalefactor, drawing, color);
  } else if (drawing.type == "polygon") {
    drawPolygonShape(ctx, drawing, color);
  } else if (drawing.type == "text") {    
    drawText(ctx, drawing, color);
  } else if (drawing.type == "polyline") {    
    drawPolylineShape(ctx, drawing, color);
  }
}

function getCirclePath(radius) {
  var path = new Path2D();
  path.arc(0, 0, radius, 0, 2 * Math.PI);
  path.closePath();
  return path;
}

function getCachedPadPath(pad) {
  if (!pad.path2d) {
    // if path2d is not set, build one and cache it on pad object
    if (pad.shape == "rect") {
      pad.path2d = new Path2D();
      pad.path2d.rect(...pad.size.map(c => -c * 0.5), ...pad.size);
    } else if (pad.shape == "oval") {
      pad.path2d = getOblongPath(pad.size);
    } else if (pad.shape == "circle") {
      pad.path2d = getCirclePath(pad.size[0] / 2);
    } else if (pad.shape == "roundrect") {
      pad.path2d = getChamferedRectPath(pad.size, pad.radius, 0, 0);
    } else if (pad.shape == "chamfrect") {
      pad.path2d = getChamferedRectPath(pad.size, pad.radius, pad.chamfpos, pad.chamfratio)
    } else if (pad.shape == "custom") {
      pad.path2d = getPolygonsPath(pad);
    } else if(pad.shape === "polygon") {      
      pad.path2d = new Path2D();
      if(pad.polygon.length > 1) {
        const pos = {
          x: pad.pos[0],
          y: pad.pos[1]
        };

        pad.path2d.moveTo(pad.polygon[0].x - pos.x, pad.polygon[0].y - pos.y);
        for (var i = 1; i < pad.polygon.length; i++) {
          const point = pad.polygon[i];          
          pad.path2d.lineTo(point.x - pos.x, point.y - pos.y);
        }
        pad.path2d.closePath();              
      }
    }
  }
  return pad.path2d;
}

function drawPad(ctx, pad, color, outline) {
  ctx.save();
  ctx.translate(...pad.pos);
  if(pad.shape !== 'polygon') {
    ctx.rotate(deg2rad(pad.angle));  
  } else {
    ctx.rotate(deg2rad(0));  
  }
  
  if (pad.offset) {
    ctx.translate(...pad.offset);
  }
  ctx.fillStyle = color;
  ctx.strokeStyle = color;
  var path = getCachedPadPath(pad);
  if (outline) {
    ctx.stroke(path);
  } else {
    ctx.fill(path);
  }
  ctx.restore();
}

function drawPadHole(ctx, pad, padHoleColor) {
  if (pad.type != "th") {
    return
  };

  ctx.save();
  ctx.translate(pad.holeCenterPoint.x, pad.holeCenterPoint.y);    
  ctx.rotate(deg2rad(pad.angle));
  ctx.fillStyle = padHoleColor;
  if (pad.drillshape == "oblong") {
    ctx.fill(getOblongPath(pad.drillsize));
  } else {
    ctx.fill(getCirclePath(pad.drillsize[0] / 2));
  }
  ctx.restore();
}

function drawFootprint(ctx, layer, scalefactor, footprint, padColor, padHoleColor, outlineColor, highlight, outline) {
  if (highlight) {
    // draw bounding box
    if (footprint.layer == layer) {
      ctx.save();
      ctx.globalAlpha = 0.2;
      ctx.translate(...footprint.bbox.pos);
      ctx.rotate(deg2rad(-footprint.bbox.angle));
      ctx.translate(...footprint.bbox.relpos);
      ctx.fillStyle = padColor;
      ctx.fillRect(0, 0, ...footprint.bbox.size);
      ctx.globalAlpha = 1;
      ctx.strokeStyle = padColor;
      ctx.strokeRect(0, 0, ...footprint.bbox.size);
      ctx.restore();
    }
  }
  // draw drawings
  for (var drawing of footprint.drawings) {
    if (drawing.layer == layer) {
      drawDrawing(ctx, scalefactor, drawing.drawing, padColor);
    }
  }
  // draw pads
  if (settings.renderPads) {
    for (var pad of footprint.pads) {
      if (pad.layers.includes(layer)) {
        drawPad(ctx, pad, padColor, outline);
        if (pad.pin1 && settings.highlightpin1) {
          drawPad(ctx, pad, outlineColor, true);
        }
      }
    }
    for (var pad of footprint.pads) {
      drawPadHole(ctx, pad, padHoleColor);
    }
  }
}

function drawCrosshair(canvas, x, y, scalefactor, color) {
  if(!settings.showCrosshair) {
    return;
  }

  var ctx = canvas.getContext("2d");

  HTMLFormControlsCollection.log
  
  ctx.save();
  ctx.globalAlpha = 0.75;
  ctx.lineWidth = 2 / scalefactor;
  ctx.strokeStyle = color;

  // TODO: Should calculate a proper bbox for the view port.
  const dummyOffset = 4000;

  // horz line
  ctx.beginPath();
  ctx.moveTo(x - dummyOffset, y);
  ctx.lineTo(x + dummyOffset, y);
  ctx.stroke();

  // vert line
  ctx.beginPath();
  ctx.moveTo(x, y - dummyOffset);
  ctx.lineTo(x, y + dummyOffset);
  ctx.stroke();

  ctx.globalAlpha = 1;
  ctx.restore();
}

function drawEdgeCuts(canvas, scalefactor) {
  var ctx = canvas.getContext("2d");
  var edgecolor = getComputedStyle(topmostdiv).getPropertyValue('--pcb-edge-color');
  for (var edge of pcbdata.edges) {
    drawedge(ctx, scalefactor, edge, edgecolor);
  }
}

function drawOrphanPads(canvas, layer, scalefactor, highlight, highlightedPads) {
  if (!settings.renderPads) {
    return;
  }

  highlightedPads = highlightedPads || [];

  var ctx = canvas.getContext("2d");
  ctx.save();
  ctx.lineWidth = 3 / scalefactor;
  var style = getComputedStyle(topmostdiv);
  var padColor = style.getPropertyValue('--pad-color');
  var padHoleColor = style.getPropertyValue('--pad-hole-color');  
  if (highlight) {
    padColor = style.getPropertyValue('--pad-color-highlight');
    outlineColor = style.getPropertyValue('--pin1-outline-color-highlight');
  }

  var i = 0;
  for (var pad of pcbdata.pads) {
    if (pad.layers.includes(layer)) {
      var outline = settings.renderDnpOutline;
      if (!highlight || highlightedPads.includes(i)) {
        drawPad(ctx, pad, padColor, outline); 
      }     
    }

    i++;
  }
  
  for (var pad of pcbdata.pads) {
    drawPadHole(ctx, pad, padHoleColor);
  }  

  ctx.restore();
}

function drawFootprints(canvas, layer, scalefactor, highlight) {
  var ctx = canvas.getContext("2d");
  ctx.save();
  ctx.lineWidth = 3 / scalefactor;
  var style = getComputedStyle(topmostdiv);
  var padColor = style.getPropertyValue('--pad-color');
  var padHoleColor = style.getPropertyValue('--pad-hole-color');
  var outlineColor = style.getPropertyValue('--pin1-outline-color');
  if (highlight) {
    padColor = style.getPropertyValue('--pad-color-highlight');
    outlineColor = style.getPropertyValue('--pin1-outline-color-highlight');
  }
  for (var i = 0; i < pcbdata.footprints.length; i++) {
    var mod = pcbdata.footprints[i];
    var outline = settings.renderDnpOutline && pcbdata.bom.skipped.includes(i);
    if (!highlight || highlightedFootprints.includes(i)) {
      drawFootprint(ctx, layer, scalefactor, mod, padColor, padHoleColor, outlineColor, highlight, outline);
    }
  }
  ctx.restore();
}

function drawBgLayer(layername, canvas, layer, scalefactor, edgeColor, polygonColor, textColor) {
  var ctx = canvas.getContext("2d");
  for (var d of pcbdata.drawings[layername][layer]) {
    if (["segment", "arc", "circle", "curve", "rect","polyline"].includes(d.type)) {
      drawedge(ctx, scalefactor, d, edgeColor);
    } else if (d.type == "polygon") {
      drawPolygonShape(ctx, d, polygonColor);
    } else if (d.type == "text") {
      drawText(ctx, d, polygonColor);
    }
  }
}

function drawTracks(canvas, layer, color, highlight) {
  ctx = canvas.getContext("2d");
  ctx.strokeStyle = color;
  ctx.lineCap = "round";
  for(var track of pcbdata.tracks[layer]) {
    if (highlight && highlightedNet != track.net) continue;
    ctx.lineWidth = track.width;

    if(track.type === 'polyline') {
      drawPolylineShape(ctx,track,color);
    } else if(track.type === 'polygon') {
      drawPolygonShape(ctx, track, color);
    } else if(track.type === 'text') {
      drawText(ctx, track, color);
    } else {
      if ('radius' in track) {
        ctx.beginPath();
        ctx.arc(
            ...track.center,
            track.radius,
            deg2rad(track.startangle),
            deg2rad(track.endangle));
        ctx.stroke();
      } else if('start' in track && 'end' in track) {
        ctx.beginPath();
        ctx.moveTo(...track.start);
        ctx.lineTo(...track.end);
        ctx.stroke();
      }
      
    }

  }
}

function drawZones(canvas, layer, color, highlight) {
  ctx = canvas.getContext("2d");
  ctx.save();
  ctx.strokeStyle = color;
  ctx.fillStyle = color;
  ctx.lineJoin = "round";
  for(var zone of pcbdata.zones[layer]) {
    if (!zone.path2d) {
      zone.path2d = getPolygonsPath(zone);
    }
    if (highlight && highlightedNet != zone.net) continue;
    ctx.fill(zone.path2d);
    if (zone.width > 0) {
      ctx.lineWidth = zone.width;
      ctx.stroke(zone.path2d);
    }
  }
  ctx.restore();
}

function clearCanvas(canvas, color = null) {
  var ctx = canvas.getContext("2d");
  ctx.save();
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  if (color) {
    ctx.fillStyle = color;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
  } else {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
  }
  ctx.restore();
}

function drawNets(canvas, layer, highlight) {
  var style = getComputedStyle(topmostdiv);
  if (settings.renderTracks) {
    var trackColor = style.getPropertyValue(highlight ? '--track-color-highlight' : '--track-color');
    drawTracks(canvas, layer, trackColor, highlight);
  }
  if (settings.renderZones) {
    var zoneColor = style.getPropertyValue(highlight ? '--zone-color-highlight' : '--zone-color');
    drawZones(canvas, layer, zoneColor, highlight);
  }
  if (highlight && settings.renderPads) {
    var padColor = style.getPropertyValue('--pad-color-highlight');
    var padHoleColor = style.getPropertyValue('--pad-hole-color');
    var ctx = canvas.getContext("2d");
    for (var footprint of pcbdata.footprints) {
      // draw pads
      var padDrawn = false;
      for (var pad of footprint.pads) {
        if (highlightedNet != pad.net) continue;
        if (pad.layers.includes(layer)) {
          drawPad(ctx, pad, padColor, false);
          padDrawn = true;
        }
      }
      if (padDrawn) {
        // redraw all pad holes because some pads may overlap
        for (var pad of footprint.pads) {
          drawPadHole(ctx, pad, padHoleColor);
        }
      }
    }
  }
}

function drawHighlightsOnLayer(canvasdict, clear = true) {
  if (clear) {
    clearCanvas(canvasdict.highlight);
  }
  
  if (highlightedFootprints.length > 0) {
    drawFootprints(canvasdict.highlight, canvasdict.layer,
      canvasdict.transform.s * canvasdict.transform.zoom, true);
  }

  // Draw crosshairs
  if (highlightedFootprints.length > 0) {
    for(var i = 0; i < pcbdata.footprints.length; i++) {
      const footprint = pcbdata.footprints[i];
      if(highlightedFootprints.includes(i) && footprint.layer === canvasdict.layer) {
        drawCrosshair(canvasdict.highlight, footprint.center[0], footprint.center[1], canvasdict.transform.s * canvasdict.transform.zoom, 'red');
      }
    }
  }

  if (highlightedNet !== null) {
    var highlightedPads = [];
    for(var i = 0; i < pcbdata.pads.length; i++) {
      var pad = pcbdata.pads[i];
      if(pad.net === highlightedNet) {
        highlightedPads.push(i);
      }
    }
    
    drawOrphanPads(canvasdict.highlight, canvasdict.layer,
      canvasdict.transform.s * canvasdict.transform.zoom, true, highlightedPads);

    drawNets(canvasdict.highlight, canvasdict.layer, true);
  }
}

function drawHighlights() {
  drawHighlightsOnLayer(allcanvas.front);
  drawHighlightsOnLayer(allcanvas.back);
}

function drawBackground(canvasdict, clear = true) {
  if (clear) {
    clearCanvas(canvasdict.bg);
    clearCanvas(canvasdict.fab);
    clearCanvas(canvasdict.silk);
  }

  drawNets(canvasdict.bg, canvasdict.layer, false);

  drawOrphanPads(canvasdict.bg, canvasdict.layer, canvasdict.transform.s * canvasdict.transform.zoom, false);

  drawFootprints(canvasdict.bg, canvasdict.layer, canvasdict.transform.s * canvasdict.transform.zoom, false);

  drawEdgeCuts(canvasdict.bg, canvasdict.transform.s);

  var style = getComputedStyle(topmostdiv);
  var edgeColor = style.getPropertyValue('--silkscreen-edge-color');
  var polygonColor = style.getPropertyValue('--silkscreen-polygon-color');
  var textColor = style.getPropertyValue('--silkscreen-text-color');
  if (settings.renderSilkscreen) {
    drawBgLayer(
      "silkscreen", canvasdict.silk, canvasdict.layer,
      canvasdict.transform.s * canvasdict.transform.zoom,
      edgeColor, polygonColor, textColor);
  }
  edgeColor = style.getPropertyValue('--fabrication-edge-color');
  polygonColor = style.getPropertyValue('--fabrication-polygon-color');
  textColor = style.getPropertyValue('--fabrication-text-color');
  if (settings.renderFabrication) {
    drawBgLayer(
      "fabrication", canvasdict.fab, canvasdict.layer,
      canvasdict.transform.s * canvasdict.transform.zoom,
      edgeColor, polygonColor, textColor);
  }
}

function prepareCanvas(canvas, flip, transform) {
  var ctx = canvas.getContext("2d");
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  var fontsize = 1.55;
  ctx.scale(transform.zoom, transform.zoom);
  ctx.translate(transform.panx, transform.pany);
  if (flip) {
    ctx.scale(-1, 1);
  }
  ctx.translate(transform.x, transform.y);
  ctx.rotate(deg2rad(settings.boardRotation));
  ctx.scale(transform.s, transform.s);
}

function prepareLayer(canvasdict) {
  var flip = (canvasdict.layer == "B");
  for (var c of ["bg", "fab", "silk", "highlight"]) {
    prepareCanvas(canvasdict[c], flip, canvasdict.transform);
  }
}

function rotateVector(v, angle) {
  angle = deg2rad(angle);
  return [
    v[0] * Math.cos(angle) - v[1] * Math.sin(angle),
    v[0] * Math.sin(angle) + v[1] * Math.cos(angle)
  ];
}

function applyRotation(bbox) {
  var corners = [
    [bbox.minx, bbox.miny],
    [bbox.minx, bbox.maxy],
    [bbox.maxx, bbox.miny],
    [bbox.maxx, bbox.maxy],
  ];
  corners = corners.map((v) => rotateVector(v, settings.boardRotation));
  return {
    minx: corners.reduce((a, v) => Math.min(a, v[0]), Infinity),
    miny: corners.reduce((a, v) => Math.min(a, v[1]), Infinity),
    maxx: corners.reduce((a, v) => Math.max(a, v[0]), -Infinity),
    maxy: corners.reduce((a, v) => Math.max(a, v[1]), -Infinity),
  }
}

function recalcLayerScale(layerdict, width, height) {
  var bbox = applyRotation(pcbdata.edges_bbox);
  var scalefactor = 0.98 * Math.min(
    width / (bbox.maxx - bbox.minx),
    height / (bbox.maxy - bbox.miny)
  );
  if (scalefactor < 0.1) {
    scalefactor = 1;
  }
  layerdict.transform.s = scalefactor;
  var flip = (layerdict.layer == "B");
  if (flip) {
    layerdict.transform.x = -((bbox.maxx + bbox.minx) * scalefactor + width) * 0.5;
  } else {
    layerdict.transform.x = -((bbox.maxx + bbox.minx) * scalefactor - width) * 0.5;
  }
  layerdict.transform.y = -((bbox.maxy + bbox.miny) * scalefactor - height) * 0.5;
  for (var c of ["bg", "fab", "silk", "highlight"]) {
    canvas = layerdict[c];
    canvas.width = width;
    canvas.height = height;
    canvas.style.width = (width / devicePixelRatio) + "px";
    canvas.style.height = (height / devicePixelRatio) + "px";
  }
}

function redrawCanvas(layerdict) {
  prepareLayer(layerdict);
  drawBackground(layerdict);
  drawHighlightsOnLayer(layerdict);
}

function resizeCanvas(layerdict) {
  var canvasdivid = {
    "F": "frontcanvas",
    "B": "backcanvas"
  } [layerdict.layer];
  var width = document.getElementById(canvasdivid).clientWidth * devicePixelRatio;
  var height = document.getElementById(canvasdivid).clientHeight * devicePixelRatio;
  recalcLayerScale(layerdict, width, height);
  redrawCanvas(layerdict);
}

function resizeAll() {
  resizeCanvas(allcanvas.front);
  resizeCanvas(allcanvas.back);
}

function pointWithinDistanceToSegment(x, y, x1, y1, x2, y2, d) {
  var A = x - x1;
  var B = y - y1;
  var C = x2 - x1;
  var D = y2 - y1;

  var dot = A * C + B * D;
  var len_sq = C * C + D * D;
  var dx, dy;
  if (len_sq == 0) {
    // start and end of the segment coincide
    dx = x - x1;
    dy = y - y1;
  } else {
    var param = dot / len_sq;
    var xx, yy;
    if (param < 0) {
      xx = x1;
      yy = y1;
    } else if (param > 1) {
      xx = x2;
      yy = y2;
    } else {
      xx = x1 + param * C;
      yy = y1 + param * D;
    }
    dx = x - xx;
    dy = y - yy;
  }
  return dx * dx + dy * dy <= d * d;
}

function modulo(n, mod) {
  return ((n % mod) + mod ) % mod;
}

function pointWithinDistanceToArc(x, y, xc, yc, radius, startangle, endangle, d) {
  var dx = x - xc;
  var dy = y - yc;
  var r_sq = dx * dx + dy * dy;
  var rmin = Math.max(0, radius-d);
  var rmax = radius + d;

  if (r_sq < rmin * rmin || r_sq > rmax * rmax)
    return false;

  var angle1 = modulo(deg2rad(startangle), 2 * Math.PI);
  var dx1 = xc + radius * Math.cos(angle1) - x;
  var dy1 = yc + radius * Math.sin(angle1) - y;
  if (dx1 * dx1 + dy1 * dy1 <= d * d)
    return true;

  var angle2 = modulo(deg2rad(endangle), 2 * Math.PI);
  var dx2 = xc + radius * Math.cos(angle2) - x;
  var dy2 = yc + radius * Math.sin(angle2) - y;
  if (dx2 * dx2 + dy2 * dy2 <= d * d)
    return true;

  var angle = modulo(Math.atan2(dy, dx), 2 * Math.PI);
  if (angle1 > angle2)
    return (angle >= angle2 || angle <= angle1);
  else
    return (angle >= angle1 && angle <= angle2);
}

function pointWithinPad(x, y, pad) {
  var v = [x - pad.pos[0], y - pad.pos[1]];
  v = rotateVector(v, -pad.angle);
  if (pad.offset) {
    v[0] -= pad.offset[0];
    v[1] -= pad.offset[1];
  }
  return emptyContext2d.isPointInPath(getCachedPadPath(pad), ...v);
}

function netHitScan(layer, x, y) {
  // TODO: Should be refactored

  // Check track segments
  if (settings.renderTracks && pcbdata.tracks) {
    for(var track of pcbdata.tracks[layer]) {
      if(track.type === 'polyline') {
        const path = getPolygonsPath(track);
        if(path) {
          hitTestContext2d.save();
          hitTestContext2d.lineWidth = track.width;
          
          if(hitTestContext2d.isPointInStroke(path,x,y)) {
            hitTestContext2d.restore();
            return track.net;            
          }
          
          hitTestContext2d.restore();          
        }        

      } else if(track.type === 'polygon') {
        const path = getPolygonsPath(track);
        if(path && hitTestContext2d.isPointInPath(path,x,y)) {          
          return track.net;                      
        }                     
      } else if(track.type === 'text') {
        // TODO: To implement taking in account TrueType fonts.
      } else if ('radius' in track) {
        if (pointWithinDistanceToArc(x, y, ...track.center, track.radius, track.startangle, track.endangle, track.width / 2)) {
          return track.net;
        }
      } else if('start' in track && 'end' in track) {
        if (pointWithinDistanceToSegment(x, y, ...track.start, ...track.end, track.width / 2)) {
          return track.net;
        }
      }
    }
  }
  // Check pads
  if (settings.renderPads) {

    // Footprints containing pads
    for (var footprint of pcbdata.footprints) {
      for(var pad of footprint.pads) {
        if (pad.layers.includes(layer) && pointWithinPad(x, y, pad)) {
          return pad.net;
        }
      }
    }

    // Orphan pads
    for(var pad of pcbdata.pads) {
      if (pad.layers.includes(layer) && pointWithinPad(x, y, pad)) {
        return pad.net;
      }
    }
  }
  return null;
}

function pointWithinFootprintBbox(x, y, bbox) {
  var v = [x - bbox.pos[0], y - bbox.pos[1]];
  v = rotateVector(v, bbox.angle);
  return bbox.relpos[0] <= v[0] && v[0] <= bbox.relpos[0] + bbox.size[0] &&
         bbox.relpos[1] <= v[1] && v[1] <= bbox.relpos[1] + bbox.size[1];
}

function bboxHitScan(layer, x, y) {
  var result = [];
  for (var i = 0; i < pcbdata.footprints.length; i++) {
    var footprint = pcbdata.footprints[i];
    if (footprint.layer == layer) {
      if (pointWithinFootprintBbox(x, y, footprint.bbox)) {
        result.push(i);
      }
    }
  }
  return result;
}

function handlePointerDown(e, layerdict) {
  if (e.button != 0 && e.button != 1) {
    return;
  }
  e.preventDefault();
  e.stopPropagation();

  if (!e.hasOwnProperty("offsetX")) {
    // The polyfill doesn't set this properly
    e.offsetX = e.pageX - e.currentTarget.offsetLeft;
    e.offsetY = e.pageY - e.currentTarget.offsetTop;
  }

  layerdict.pointerStates[e.pointerId] = {
    distanceTravelled: 0,
    lastX: e.offsetX,
    lastY: e.offsetY,
    downTime: Date.now(),
  };
}

function handleMouseClick(e, layerdict) {
  if (!e.hasOwnProperty("offsetX")) {
    // The polyfill doesn't set this properly
    e.offsetX = e.pageX - e.currentTarget.offsetLeft;
    e.offsetY = e.pageY - e.currentTarget.offsetTop;
  }

  var x = e.offsetX;
  var y = e.offsetY;
  var t = layerdict.transform;
  if (layerdict.layer == "B") {
    x = (devicePixelRatio * x / t.zoom - t.panx + t.x) / -t.s;
  } else {
    x = (devicePixelRatio * x / t.zoom - t.panx - t.x) / t.s;
  }
  y = (devicePixelRatio * y / t.zoom - t.y - t.pany) / t.s;
  var v = rotateVector([x, y], -settings.boardRotation);
  if ("nets" in pcbdata) {
    var net = netHitScan(layerdict.layer, ...v);
    if (net !== highlightedNet) {
      netClicked(net);
    }
  }
  if (highlightedNet === null) {
    var footprints = bboxHitScan(layerdict.layer, ...v);
    if (footprints.length > 0) {
      footprintsClicked(footprints);
    }
  }
}

function handlePointerLeave(e, layerdict) {
  e.preventDefault();
  e.stopPropagation();

  if (!settings.redrawOnDrag) {
    redrawCanvas(layerdict);
  }

  delete layerdict.pointerStates[e.pointerId];
}

function resetTransform(layerdict) {
  layerdict.transform.panx = 0;
  layerdict.transform.pany = 0;
  layerdict.transform.zoom = 1;
  redrawCanvas(layerdict);
}

function handlePointerUp(e, layerdict) {
  if (!e.hasOwnProperty("offsetX")) {
    // The polyfill doesn't set this properly
    e.offsetX = e.pageX - e.currentTarget.offsetLeft;
    e.offsetY = e.pageY - e.currentTarget.offsetTop;
  }

  e.preventDefault();
  e.stopPropagation();

  if (e.button == 2) {
    // Reset pan and zoom on right click.
    resetTransform(layerdict);
    layerdict.anotherPointerTapped = false;
    return;
  }

  // We haven't necessarily had a pointermove event since the interaction started, so make sure we update this now
  var ptr = layerdict.pointerStates[e.pointerId];
  ptr.distanceTravelled += Math.abs(e.offsetX - ptr.lastX) + Math.abs(e.offsetY - ptr.lastY);

  if (e.button == 0 && ptr.distanceTravelled < 10 && Date.now() - ptr.downTime <= 500) {
    if (Object.keys(layerdict.pointerStates).length == 1) {
      if (layerdict.anotherPointerTapped) {
        // This is the second pointer coming off of a two-finger tap
        resetTransform(layerdict);
      } else {
        // This is just a regular tap
        handleMouseClick(e, layerdict);
      }
      layerdict.anotherPointerTapped = false;
    } else {
      // This is the first finger coming off of what could become a two-finger tap
      layerdict.anotherPointerTapped = true;
    }
  } else {
    if (!settings.redrawOnDrag) {
      redrawCanvas(layerdict);
    }
    layerdict.anotherPointerTapped = false;
  }

  delete layerdict.pointerStates[e.pointerId];
}

function handlePointerMove(e, layerdict) {
  if (!layerdict.pointerStates.hasOwnProperty(e.pointerId)) {
    return;
  }
  e.preventDefault();
  e.stopPropagation();

  if (!e.hasOwnProperty("offsetX")) {
    // The polyfill doesn't set this properly
    e.offsetX = e.pageX - e.currentTarget.offsetLeft;
    e.offsetY = e.pageY - e.currentTarget.offsetTop;
  }

  var thisPtr = layerdict.pointerStates[e.pointerId];

  var dx = e.offsetX - thisPtr.lastX;
  var dy = e.offsetY - thisPtr.lastY;

  // If this number is low on pointer up, we count the action as a click
  thisPtr.distanceTravelled += Math.abs(dx) + Math.abs(dy);

  if (Object.keys(layerdict.pointerStates).length == 1) {
    // This is a simple drag
    layerdict.transform.panx += devicePixelRatio * dx / layerdict.transform.zoom;
    layerdict.transform.pany += devicePixelRatio * dy / layerdict.transform.zoom;
  } else if (Object.keys(layerdict.pointerStates).length == 2) {
    var otherPtr = Object.values(layerdict.pointerStates).filter((ptr) => ptr != thisPtr)[0];

    var oldDist = Math.sqrt(Math.pow(thisPtr.lastX - otherPtr.lastX, 2) + Math.pow(thisPtr.lastY - otherPtr.lastY, 2));
    var newDist = Math.sqrt(Math.pow(e.offsetX - otherPtr.lastX, 2)     + Math.pow(e.offsetY - otherPtr.lastY, 2));

    var scaleFactor = newDist/oldDist;

    if (scaleFactor != NaN) {
      layerdict.transform.zoom *= scaleFactor;

      var zoomd = (1 - scaleFactor) / layerdict.transform.zoom;
      layerdict.transform.panx += devicePixelRatio * otherPtr.lastX * zoomd;
      layerdict.transform.pany += devicePixelRatio * otherPtr.lastY * zoomd;
    }
  }

  thisPtr.lastX = e.offsetX;
  thisPtr.lastY = e.offsetY;

  if (settings.redrawOnDrag) {
    redrawCanvas(layerdict);
  }
}

function handleMouseWheel(e, layerdict) {
  e.preventDefault();
  e.stopPropagation();
  var t = layerdict.transform;
  var wheeldelta = e.deltaY;
  if (e.deltaMode == 1) {
    // FF only, scroll by lines
    wheeldelta *= 30;
  } else if (e.deltaMode == 2) {
    wheeldelta *= 300;
  }
  var m = Math.pow(1.1, -wheeldelta / 40);
  // Limit amount of zoom per tick.
  if (m > 2) {
    m = 2;
  } else if (m < 0.5) {
    m = 0.5;
  }
  t.zoom *= m;
  var zoomd = (1 - m) / t.zoom;
  t.panx += devicePixelRatio * e.offsetX * zoomd;
  t.pany += devicePixelRatio * e.offsetY * zoomd;
  redrawCanvas(layerdict);
}

function addMouseHandlers(div, layerdict) {
  div.addEventListener("pointerdown", function(e) {
    handlePointerDown(e, layerdict);
  });
  div.addEventListener("pointermove", function(e) {
    handlePointerMove(e, layerdict);
  });
  div.addEventListener("pointerup", function(e) {
    handlePointerUp(e, layerdict);
  });
  var pointerleave = function(e) {
    handlePointerLeave(e, layerdict);
  }
  div.addEventListener("pointercancel", pointerleave);
  div.addEventListener("pointerleave", pointerleave);
  div.addEventListener("pointerout", pointerleave);

  div.onwheel = function(e) {
    handleMouseWheel(e, layerdict);
  }
  for (var element of [div, layerdict.bg, layerdict.fab, layerdict.silk, layerdict.highlight]) {
    element.addEventListener("contextmenu", function(e) {
      e.preventDefault();
    }, false);
  }
}

function setRedrawOnDrag(value) {
  settings.redrawOnDrag = value;
  writeStorage("redrawOnDrag", value);
}

function setShowCrosshair(value) {
  settings.showCrosshair = value;
  writeStorage("showCrosshair", value);
}

function setBoardRotation(value) {
  settings.boardRotation = value * 5;
  writeStorage("boardRotation", settings.boardRotation);
  document.getElementById("rotationDegree").textContent = settings.boardRotation;
  resizeAll();
}

function initRender() {
  allcanvas = {
    front: {
      transform: {
        x: 0,
        y: 0,
        s: 1,
        panx: 0,
        pany: 0,
        zoom: 1,
      },
      pointerStates: {},
      anotherPointerTapped: false,
      bg: document.getElementById("F_bg"),
      fab: document.getElementById("F_fab"),
      silk: document.getElementById("F_slk"),
      highlight: document.getElementById("F_hl"),
      layer: "F",
    },
    back: {
      transform: {
        x: 0,
        y: 0,
        s: 1,
        panx: 0,
        pany: 0,
        zoom: 1,
      },
      pointerStates: {},
      anotherPointerTapped: false,
      bg: document.getElementById("B_bg"),
      fab: document.getElementById("B_fab"),
      silk: document.getElementById("B_slk"),
      highlight: document.getElementById("B_hl"),
      layer: "B",
    }
  };
  addMouseHandlers(document.getElementById("frontcanvas"), allcanvas.front);
  addMouseHandlers(document.getElementById("backcanvas"), allcanvas.back);
}

///////////////////////////////////////////////

///////////////////////////////////////////////
/* DOM manipulation and misc code */

var bomsplit;
var canvassplit;
var initDone = false;
var bomSortFunction = null;
var currentSortColumn = null;
var currentSortOrder = null;
var currentHighlightedRowId;
var highlightHandlers = [];
var footprintIndexToHandler = {};
var netsToHandler = {};
var highlightedFootprints = [];
var highlightedOrphanPads = [];
var highlightedNet = null;
var lastClicked;

function dbg(html) {
  dbgdiv.innerHTML = html;
}

function redrawIfInitDone() {
  if (initDone) {
    redrawCanvas(allcanvas.front);
    redrawCanvas(allcanvas.back);
  }
}

function padsVisible(value) {
  writeStorage("padsVisible", value);
  settings.renderPads = value;
  redrawIfInitDone();
}

function referencesVisible(value) {
  writeStorage("referencesVisible", value);
  settings.renderReferences = value;
  redrawIfInitDone();
}

function valuesVisible(value) {
  writeStorage("valuesVisible", value);
  settings.renderValues = value;
  redrawIfInitDone();
}

function tracksVisible(value) {
  writeStorage("tracksVisible", value);
  settings.renderTracks = value;
  redrawIfInitDone();
}

function zonesVisible(value) {
  writeStorage("zonesVisible", value);
  settings.renderZones = value;
  redrawIfInitDone();
}

function dnpOutline(value) {
  writeStorage("dnpOutline", value);
  settings.renderDnpOutline = value;
  redrawIfInitDone();
}

function setDarkMode(value) {
  if (value) {
    topmostdiv.classList.add("dark");
  } else {
    topmostdiv.classList.remove("dark");
  }
  writeStorage("darkmode", value);
  settings.darkMode = value;
  redrawIfInitDone();
}

function setFullscreen(value) {
  if (value) {
    document.documentElement.requestFullscreen();
  } else {
    document.exitFullscreen();
  }
}

function fabricationVisible(value) {
  writeStorage("fabricationVisible", value);
  settings.renderFabrication = value;
  redrawIfInitDone();
}

function silkscreenVisible(value) {
  writeStorage("silkscreenVisible", value);
  settings.renderSilkscreen = value;
  redrawIfInitDone();
}

function setHighlightPin1(value) {
  writeStorage("highlightpin1", value);
  settings.highlightpin1 = value;
  redrawIfInitDone();
}

function getStoredCheckboxRefs(checkbox) {
  function convert(ref) {
    var intref = parseInt(ref);
    if (isNaN(intref)) {
      for (var i = 0; i < pcbdata.footprints.length; i++) {
        if (pcbdata.footprints[i].ref == ref) {
          return i;
        }
      }
      return -1;
    } else {
      return intref;
    }
  }
  if (!(checkbox in settings.checkboxStoredRefs)) {
    var val = readStorage("checkbox_" + checkbox);
    settings.checkboxStoredRefs[checkbox] = val ? val : "";
  }
  if (!settings.checkboxStoredRefs[checkbox]) {
    return new Set();
  } else {
    return new Set(settings.checkboxStoredRefs[checkbox].split(",").map(r => convert(r)).filter(a => a >= 0));
  }
}

function getCheckboxState(checkbox, references) {
  var storedRefsSet = getStoredCheckboxRefs(checkbox);
  var currentRefsSet = new Set(references.map(r => r[1]));
  // Get difference of current - stored
  var difference = new Set(currentRefsSet);
  for (ref of storedRefsSet) {
    difference.delete(ref);
  }
  if (difference.size == 0) {
    // All the current refs are stored
    return "checked";
  } else if (difference.size == currentRefsSet.size) {
    // None of the current refs are stored
    return "unchecked";
  } else {
    // Some of the refs are stored
    return "indeterminate";
  }
}

function setBomCheckboxState(checkbox, element, references) {
  var state = getCheckboxState(checkbox, references);
  element.checked = (state == "checked");
  element.indeterminate = (state == "indeterminate");
}

function createCheckboxChangeHandler(checkbox, references, row) {
  return function() {
    refsSet = getStoredCheckboxRefs(checkbox);
    var darkenWhenChecked = settings.darkenWhenChecked == checkbox;
    eventArgs = {
      checkbox: checkbox,
      refs: references,
    }
    if (this.checked) {
      // checkbox ticked
      for (var ref of references) {
        refsSet.add(ref[1]);
      }
      if (darkenWhenChecked) {
        row.classList.add("checked");
      }
      eventArgs.state = 'checked';
    } else {
      // checkbox unticked
      for (var ref of references) {
        refsSet.delete(ref[1]);
      }
      if (darkenWhenChecked) {
        row.classList.remove("checked");
      }
      eventArgs.state = 'unchecked';
    }
    settings.checkboxStoredRefs[checkbox] = [...refsSet].join(",");
    writeStorage("checkbox_" + checkbox, settings.checkboxStoredRefs[checkbox]);
    updateCheckboxStats(checkbox);
    EventHandler.emitEvent(IBOM_EVENT_TYPES.CHECKBOX_CHANGE_EVENT, eventArgs);
  }
}

function clearHighlightedFootprints() {
  if (currentHighlightedRowId) {
    document.getElementById(currentHighlightedRowId).classList.remove("highlighted");
    currentHighlightedRowId = null;
    highlightedFootprints = [];  
    highlightedOrphanPads = [];  
    highlightedNet = null;
  }
}

function createRowHighlightHandler(rowid, refs, net) {
  return function() {
    if (currentHighlightedRowId) {
      if (currentHighlightedRowId == rowid) {
        return;
      }
      document.getElementById(currentHighlightedRowId).classList.remove("highlighted");
    }
    document.getElementById(rowid).classList.add("highlighted");
    currentHighlightedRowId = rowid;
    highlightedFootprints = refs ? refs.map(r => r[1]) : [];
    highlightedNet = net;
    drawHighlights();
    EventHandler.emitEvent(
      IBOM_EVENT_TYPES.HIGHLIGHT_EVENT,
      {
        rowid: rowid,
        refs: refs,
        net: net
      });
  }
}

function entryMatches(entry) {
  if (settings.bommode == "netlist") {
    // entry is just a net name
    return entry.toLowerCase().indexOf(filter) >= 0;
  }
  // check refs
  for (var ref of entry[3]) {
    if (ref[0].toLowerCase().indexOf(filter) >= 0) {
      return true;
    }
  }
  // check extra fields
  for (var i in config.extra_fields) {
    if (entry[4][i].toLowerCase().indexOf(filter) >= 0) {
      return true;
    }
  }
  // check value
  if (entry[1].toLowerCase().indexOf(filter) >= 0) {
    return true;
  }
  // check footprint
  if (entry[2].toLowerCase().indexOf(filter) >= 0) {
    return true;
  }
  return false;
}

function findRefInEntry(entry) {
  return entry[3].filter(r => r[0].toLowerCase() == reflookup);
}

function highlightFilter(s) {
  if (!filter) {
    return s;
  }
  var parts = s.toLowerCase().split(filter);
  if (parts.length == 1) {
    return s;
  }
  var r = "";
  var pos = 0;
  for (var i in parts) {
    if (i > 0) {
      r += '<mark class="highlight">' +
        s.substring(pos, pos + filter.length) +
        '</mark>';
      pos += filter.length;
    }
    r += s.substring(pos, pos + parts[i].length);
    pos += parts[i].length;
  }
  return r;
}

function checkboxSetUnsetAllHandler(checkboxname) {
  return function() {
    var checkboxnum = 0;
    while (checkboxnum < settings.checkboxes.length &&
      settings.checkboxes[checkboxnum].toLowerCase() != checkboxname.toLowerCase()) {
      checkboxnum++;
    }
    if (checkboxnum >= settings.checkboxes.length) {
      return;
    }
    var allset = true;
    var checkbox;
    var row;
    for (row of bombody.childNodes) {
      checkbox = row.childNodes[checkboxnum + 1].childNodes[0];
      if (!checkbox.checked || checkbox.indeterminate) {
        allset = false;
        break;
      }
    }
    for (row of bombody.childNodes) {
      checkbox = row.childNodes[checkboxnum + 1].childNodes[0];
      checkbox.checked = !allset;
      checkbox.indeterminate = false;
      checkbox.onchange();
    }
  }
}

function createColumnHeader(name, cls, comparator) {
  var th = document.createElement("TH");
  th.innerHTML = name;
  th.classList.add(cls);
  th.style.cursor = "pointer";
  var span = document.createElement("SPAN");
  span.classList.add("sortmark");
  span.classList.add("none");
  th.appendChild(span);
  th.onclick = function() {
    if (currentSortColumn && this !== currentSortColumn) {
      // Currently sorted by another column
      currentSortColumn.childNodes[1].classList.remove(currentSortOrder);
      currentSortColumn.childNodes[1].classList.add("none");
      currentSortColumn = null;
      currentSortOrder = null;
    }
    if (currentSortColumn && this === currentSortColumn) {
      // Already sorted by this column
      if (currentSortOrder == "asc") {
        // Sort by this column, descending order
        bomSortFunction = function(a, b) {
          return -comparator(a, b);
        }
        currentSortColumn.childNodes[1].classList.remove("asc");
        currentSortColumn.childNodes[1].classList.add("desc");
        currentSortOrder = "desc";
      } else {
        // Unsort
        bomSortFunction = null;
        currentSortColumn.childNodes[1].classList.remove("desc");
        currentSortColumn.childNodes[1].classList.add("none");
        currentSortColumn = null;
        currentSortOrder = null;
      }
    } else {
      // Sort by this column, ascending order
      bomSortFunction = comparator;
      currentSortColumn = this;
      currentSortColumn.childNodes[1].classList.remove("none");
      currentSortColumn.childNodes[1].classList.add("asc");
      currentSortOrder = "asc";
    }
    populateBomBody();
  }
  return th;
}

function populateBomHeader() {
  while (bomhead.firstChild) {
    bomhead.removeChild(bomhead.firstChild);
  }
  var tr = document.createElement("TR");
  var th = document.createElement("TH");
  th.classList.add("numCol");
  tr.appendChild(th);
  var checkboxCompareClosure = function(checkbox) {
    return (a, b) => {
      var stateA = getCheckboxState(checkbox, a[3]);
      var stateB = getCheckboxState(checkbox, b[3]);
      if (stateA > stateB) return -1;
      if (stateA < stateB) return 1;
      return 0;
    }
  }
  if (settings.bommode == "netlist") {
    th = createColumnHeader("Net name", "bom-netname", (a, b) => {
      if (a > b) return -1;
      if (a < b) return 1;
      return 0;
    });
    tr.appendChild(th);
  } else {
    for (var checkbox of settings.checkboxes) {
      th = createColumnHeader(
        checkbox, "bom-checkbox", checkboxCompareClosure(checkbox));
      th.onclick = fancyDblClickHandler(
        th, th.onclick.bind(th), checkboxSetUnsetAllHandler(checkbox));
      tr.appendChild(th);
    }
    tr.appendChild(createColumnHeader("References", "References", (a, b) => {
      var i = 0;
      while (i < a[3].length && i < b[3].length) {
        if (a[3][i] != b[3][i]) return a[3][i] > b[3][i] ? 1 : -1;
        i++;
      }
      return a[3].length - b[3].length;
    }));
    // Extra fields
    if (config.extra_fields.length > 0) {
      var extraFieldCompareClosure = function(fieldIndex) {
        return (a, b) => {
          var fa = a[4][fieldIndex];
          var fb = b[4][fieldIndex];
          if (fa != fb) return fa > fb ? 1 : -1;
          else return 0;
        }
      }
      for (var i in config.extra_fields) {
        tr.appendChild(createColumnHeader(
          config.extra_fields[i], "extra", extraFieldCompareClosure(i)));
      }
    }
    tr.appendChild(createColumnHeader("Value", "Value", (a, b) => {
      return valueCompare(a[5], b[5], a[1], b[1]);
    }));
    tr.appendChild(createColumnHeader("Footprint", "Footprint", (a, b) => {
      if (a[2] != b[2]) return a[2] > b[2] ? 1 : -1;
      else return 0;
    }));
    if (settings.bommode == "grouped") {
      tr.appendChild(createColumnHeader("Quantity", "Quantity", (a, b) => {
        return a[3].length - b[3].length;
      }));
    }
  }
  bomhead.appendChild(tr);
}

function populateBomBody() {
  while (bom.firstChild) {
    bom.removeChild(bom.firstChild);
  }
  highlightHandlers = [];
  footprintIndexToHandler = {};
  netsToHandler = {};
  currentHighlightedRowId = null;
  var first = true;
  if (settings.bommode == "netlist") {
    bomtable = pcbdata.nets.slice();
  } else {
    switch (settings.canvaslayout) {
      case 'F':
        bomtable = pcbdata.bom.F.slice();
        break;
      case 'FB':
        bomtable = pcbdata.bom.both.slice();
        break;
      case 'B':
        bomtable = pcbdata.bom.B.slice();
        break;
    }
    if (settings.bommode == "ungrouped") {
      // expand bom table
      expandedTable = []
      for (var bomentry of bomtable) {
        for (var ref of bomentry[3]) {
          expandedTable.push([1, bomentry[1], bomentry[2], [ref], bomentry[4], bomentry[5]]);
        }
      }
      bomtable = expandedTable;
    }
  }
  if (bomSortFunction) {
    bomtable = bomtable.sort(bomSortFunction);
  }
  for (var i in bomtable) {
    var bomentry = bomtable[i];
    if (filter && !entryMatches(bomentry)) {
      continue;
    }
    var references = null;
    var netname = null;
    var tr = document.createElement("TR");
    var td = document.createElement("TD");
    var rownum = +i + 1;
    tr.id = "bomrow" + rownum;
    td.textContent = rownum;
    tr.appendChild(td);
    if (settings.bommode == "netlist") {
      netname = bomentry;
      td = document.createElement("TD");
      td.innerHTML = highlightFilter(netname ? netname : "&lt;no net&gt;");
      tr.appendChild(td);
    } else {
      if (reflookup) {
        references = findRefInEntry(bomentry);
        if (references.length == 0) {
          continue;
        }
      } else {
        references = bomentry[3];
      }
      // Checkboxes
      for (var checkbox of settings.checkboxes) {
        if (checkbox) {
          td = document.createElement("TD");
          var input = document.createElement("input");
          input.type = "checkbox";
          input.onchange = createCheckboxChangeHandler(checkbox, references, tr);
          setBomCheckboxState(checkbox, input, references);
          if (input.checked && settings.darkenWhenChecked == checkbox) {
            tr.classList.add("checked");
          }
          td.appendChild(input);
          tr.appendChild(td);
        }
      }
      // References
      td = document.createElement("TD");
      td.innerHTML = highlightFilter(references.map(r => r[0]).join(", "));
      tr.appendChild(td);
      // Extra fields
      for (var i in config.extra_fields) {
        td = document.createElement("TD");
        td.innerHTML = highlightFilter(bomentry[4][i]);
        tr.appendChild(td);
      }
      // Value
      td = document.createElement("TD");
      td.innerHTML = highlightFilter(bomentry[1]);
      tr.appendChild(td);
      // Footprint
      td = document.createElement("TD");
      td.innerHTML = highlightFilter(bomentry[2]);
      tr.appendChild(td);
      if (settings.bommode == "grouped") {
        // Quantity
        td = document.createElement("TD");
        td.textContent = bomentry[3].length;
        tr.appendChild(td);
      }
    }
    bom.appendChild(tr);
    var handler = createRowHighlightHandler(tr.id, references, netname);
    tr.onmousemove = handler;
    highlightHandlers.push({
      id: tr.id,
      handler: handler,
    });
    if (references !== null) {
      for (var refIndex of references.map(r => r[1])) {
        footprintIndexToHandler[refIndex] = handler;
      }
    }
    if (netname !== null) {
      netsToHandler[netname] = handler;
    }
    if ((filter || reflookup) && first) {
      handler();
      first = false;
    }
  }
  EventHandler.emitEvent(
    IBOM_EVENT_TYPES.BOM_BODY_CHANGE_EVENT,
    {
      filter: filter,
      reflookup: reflookup,
      checkboxes: settings.checkboxes,
      bommode: settings.bommode,
    });
}

function highlightPreviousRow() {
  if (!currentHighlightedRowId) {
    highlightHandlers[highlightHandlers.length - 1].handler();
  } else {
    if (highlightHandlers.length > 1 &&
      highlightHandlers[0].id == currentHighlightedRowId) {
      highlightHandlers[highlightHandlers.length - 1].handler();
    } else {
      for (var i = 0; i < highlightHandlers.length - 1; i++) {
        if (highlightHandlers[i + 1].id == currentHighlightedRowId) {
          highlightHandlers[i].handler();
          break;
        }
      }
    }
  }
  smoothScrollToRow(currentHighlightedRowId);
}

function highlightNextRow() {
  if (!currentHighlightedRowId) {
    highlightHandlers[0].handler();
  } else {
    if (highlightHandlers.length > 1 &&
      highlightHandlers[highlightHandlers.length - 1].id == currentHighlightedRowId) {
      highlightHandlers[0].handler();
    } else {
      for (var i = 1; i < highlightHandlers.length; i++) {
        if (highlightHandlers[i - 1].id == currentHighlightedRowId) {
          highlightHandlers[i].handler();
          break;
        }
      }
    }
  }
  smoothScrollToRow(currentHighlightedRowId);
}

function populateBomTable() {
  populateBomHeader();
  populateBomBody();
}

function footprintsClicked(footprintIndexes) {
  var lastClickedIndex = footprintIndexes.indexOf(lastClicked);
  for (var i = 1; i <= footprintIndexes.length; i++) {
    var refIndex = footprintIndexes[(lastClickedIndex + i) % footprintIndexes.length];
    if (refIndex in footprintIndexToHandler) {
      lastClicked = refIndex;
      footprintIndexToHandler[refIndex]();
      smoothScrollToRow(currentHighlightedRowId);
      break;
    }
  }
}

function netClicked(net) {
  if (net in netsToHandler) {
    netsToHandler[net]();
    smoothScrollToRow(currentHighlightedRowId);
  } else {
    clearHighlightedFootprints();
    highlightedNet = net;
    drawHighlights();
  }
}

function updateFilter(input) {
  filter = input.toLowerCase();
  populateBomTable();
}

function updateRefLookup(input) {
  reflookup = input.toLowerCase();
  populateBomTable();
}

function changeCanvasLayout(layout) {
  document.getElementById("fl-btn").classList.remove("depressed");
  document.getElementById("fb-btn").classList.remove("depressed");
  document.getElementById("bl-btn").classList.remove("depressed");
  switch (layout) {
    case 'F':
      document.getElementById("fl-btn").classList.add("depressed");
      if (settings.bomlayout != "bom-only") {
        canvassplit.collapse(1);
      }
      break;
    case 'B':
      document.getElementById("bl-btn").classList.add("depressed");
      if (settings.bomlayout != "bom-only") {
        canvassplit.collapse(0);
      }
      break;
    default:
      document.getElementById("fb-btn").classList.add("depressed");
      if (settings.bomlayout != "bom-only") {
        canvassplit.setSizes([50, 50]);
      }
  }
  settings.canvaslayout = layout;
  writeStorage("canvaslayout", layout);
  resizeAll();
  changeBomMode(settings.bommode);
}

function populateMetadata() {
  document.getElementById("title").innerHTML = pcbdata.metadata.title;
  document.getElementById("revision").innerHTML = "Rev: " + pcbdata.metadata.revision;
  document.getElementById("company").innerHTML = pcbdata.metadata.company;
  document.getElementById("filedate").innerHTML = pcbdata.metadata.date;
  if (pcbdata.metadata.title != "") {
    document.title = pcbdata.metadata.title + " BOM";
  }
  // Calculate board stats
  var fp_f = 0, fp_b = 0, pads_f = 0, pads_b = 0, pads_th = 0;
  for (var i = 0; i < pcbdata.footprints.length; i++) {
    if (pcbdata.bom.skipped.includes(i)) continue;
    var mod = pcbdata.footprints[i];
    if (mod.layer == "F") {
      fp_f++;
    } else {
      fp_b++;
    }
    for (var pad of mod.pads) {
      if (pad.type == "th") {
        pads_th++;
      } else {
        if (pad.layers.includes("F")) {
          pads_f++;
        }
        if (pad.layers.includes("B")) {
          pads_b++;
        }
      }
    }
  }
  document.getElementById("stats-components-front").innerHTML = fp_f;
  document.getElementById("stats-components-back").innerHTML = fp_b;
  document.getElementById("stats-components-total").innerHTML = fp_f + fp_b;
  document.getElementById("stats-groups-front").innerHTML = pcbdata.bom.F.length;
  document.getElementById("stats-groups-back").innerHTML = pcbdata.bom.B.length;
  document.getElementById("stats-groups-total").innerHTML = pcbdata.bom.both.length;
  document.getElementById("stats-smd-pads-front").innerHTML = pads_f;
  document.getElementById("stats-smd-pads-back").innerHTML = pads_b;
  document.getElementById("stats-smd-pads-total").innerHTML = pads_f + pads_b;
  document.getElementById("stats-th-pads").innerHTML = pads_th;
  // Update version string
  document.getElementById("github-link").innerHTML = "InteractiveHtmlBom&nbsp;" +
    /^v\d+\.\d+/.exec(pcbdata.ibom_version)[0];
}

function changeBomLayout(layout) {
  document.getElementById("bom-btn").classList.remove("depressed");
  document.getElementById("lr-btn").classList.remove("depressed");
  document.getElementById("tb-btn").classList.remove("depressed");
  switch (layout) {
    case 'bom-only':
      document.getElementById("bom-btn").classList.add("depressed");
      if (bomsplit) {
        bomsplit.destroy();
        bomsplit = null;
        canvassplit.destroy();
        canvassplit = null;
      }
      document.getElementById("frontcanvas").style.display = "none";
      document.getElementById("backcanvas").style.display = "none";
      document.getElementById("bot").style.height = "";
      break;
    case 'top-bottom':
      document.getElementById("tb-btn").classList.add("depressed");
      document.getElementById("frontcanvas").style.display = "";
      document.getElementById("backcanvas").style.display = "";
      document.getElementById("bot").style.height = "calc(100% - 80px)";
      document.getElementById("bomdiv").classList.remove("split-horizontal");
      document.getElementById("canvasdiv").classList.remove("split-horizontal");
      document.getElementById("frontcanvas").classList.add("split-horizontal");
      document.getElementById("backcanvas").classList.add("split-horizontal");
      if (bomsplit) {
        bomsplit.destroy();
        bomsplit = null;
        canvassplit.destroy();
        canvassplit = null;
      }
      bomsplit = Split(['#bomdiv', '#canvasdiv'], {
        sizes: [50, 50],
        onDragEnd: resizeAll,
        direction: "vertical",
        gutterSize: 5
      });
      canvassplit = Split(['#frontcanvas', '#backcanvas'], {
        sizes: [50, 50],
        gutterSize: 5,
        onDragEnd: resizeAll
      });
      break;
    case 'left-right':
      document.getElementById("lr-btn").classList.add("depressed");
      document.getElementById("frontcanvas").style.display = "";
      document.getElementById("backcanvas").style.display = "";
      document.getElementById("bot").style.height = "calc(100% - 80px)";
      document.getElementById("bomdiv").classList.add("split-horizontal");
      document.getElementById("canvasdiv").classList.add("split-horizontal");
      document.getElementById("frontcanvas").classList.remove("split-horizontal");
      document.getElementById("backcanvas").classList.remove("split-horizontal");
      if (bomsplit) {
        bomsplit.destroy();
        bomsplit = null;
        canvassplit.destroy();
        canvassplit = null;
      }
      bomsplit = Split(['#bomdiv', '#canvasdiv'], {
        sizes: [50, 50],
        onDragEnd: resizeAll,
        gutterSize: 5
      });
      canvassplit = Split(['#frontcanvas', '#backcanvas'], {
        sizes: [50, 50],
        gutterSize: 5,
        direction: "vertical",
        onDragEnd: resizeAll
      });
  }
  settings.bomlayout = layout;
  writeStorage("bomlayout", layout);
  changeCanvasLayout(settings.canvaslayout);
}

function changeBomMode(mode) {
  document.getElementById("bom-grouped-btn").classList.remove("depressed");
  document.getElementById("bom-ungrouped-btn").classList.remove("depressed");
  document.getElementById("bom-netlist-btn").classList.remove("depressed");
  switch (mode) {
    case 'grouped':
      document.getElementById("bom-grouped-btn").classList.add("depressed");
      break;
    case 'ungrouped':
      document.getElementById("bom-ungrouped-btn").classList.add("depressed");
      break;
    case 'netlist':
      document.getElementById("bom-netlist-btn").classList.add("depressed");
  }
  writeStorage("bommode", mode);
  if (mode != settings.bommode) {
    settings.bommode = mode;
    bomSortFunction = null;
    currentSortColumn = null;
    currentSortOrder = null;
    clearHighlightedFootprints();
  }
  populateBomTable();
}

function focusFilterField() {
  focusInputField(document.getElementById("filter"));
}

function focusRefLookupField() {
  focusInputField(document.getElementById("reflookup"));
}

function toggleBomCheckbox(bomrowid, checkboxnum) {
  if (!bomrowid || checkboxnum > settings.checkboxes.length) {
    return;
  }
  var bomrow = document.getElementById(bomrowid);
  var checkbox = bomrow.childNodes[checkboxnum].childNodes[0];
  checkbox.checked = !checkbox.checked;
  checkbox.indeterminate = false;
  checkbox.onchange();
}

function checkBomCheckbox(bomrowid, checkboxname) {
  var checkboxnum = 0;
  while (checkboxnum < settings.checkboxes.length &&
    settings.checkboxes[checkboxnum].toLowerCase() != checkboxname.toLowerCase()) {
    checkboxnum++;
  }
  if (!bomrowid || checkboxnum >= settings.checkboxes.length) {
    return;
  }
  var bomrow = document.getElementById(bomrowid);
  var checkbox = bomrow.childNodes[checkboxnum + 1].childNodes[0];
  checkbox.checked = true;
  checkbox.indeterminate = false;
  checkbox.onchange();
}

function setBomCheckboxes(value) {
  writeStorage("bomCheckboxes", value);
  settings.checkboxes = value.split(",").filter((e) => e);
  prepCheckboxes();
  populateBomTable();
  populateDarkenWhenCheckedOptions();
}

function setDarkenWhenChecked(value) {
  writeStorage("darkenWhenChecked", value);
  settings.darkenWhenChecked = value;
  populateBomTable();
}

function prepCheckboxes() {
  var table = document.getElementById("checkbox-stats");
  while (table.childElementCount > 1) {
    table.removeChild(table.lastChild);
  }
  if (settings.checkboxes.length) {
    table.style.display = "";
  } else {
    table.style.display = "none";
  }
  for (var checkbox of settings.checkboxes) {
    var tr = document.createElement("TR");
    var td = document.createElement("TD");
    td.innerHTML = checkbox;
    tr.appendChild(td);
    td = document.createElement("TD");
    td.id = "checkbox-stats-" + checkbox;
    var progressbar = document.createElement("div");
    progressbar.classList.add("bar");
    td.appendChild(progressbar);
    var text = document.createElement("div");
    text.classList.add("text");
    td.appendChild(text);
    tr.appendChild(td);
    table.appendChild(tr);
    updateCheckboxStats(checkbox);
  }
}

function populateDarkenWhenCheckedOptions() {
  var container = document.getElementById("darkenWhenCheckedContainer");

  if (settings.checkboxes.length == 0) {
    container.parentElement.style.display = "none";
    return;
  }

  container.innerHTML = '';
  container.parentElement.style.display = "inline-block";

  function createOption(name, displayName) {
    var id = "darkenWhenChecked-" + name;

    var div = document.createElement("div");
    div.classList.add("radio-container");

    var input = document.createElement("input");
    input.type = "radio";
    input.name = "darkenWhenChecked";
    input.value = name;
    input.id = id;
    input.onchange = () => setDarkenWhenChecked(name);
    div.appendChild(input);

    // Preserve the selected element when the checkboxes change
    if (name == settings.darkenWhenChecked) {
      input.checked = true;
    }

    var label = document.createElement("label");
    label.innerHTML = displayName;
    label.htmlFor = id;
    div.appendChild(label);

    container.appendChild(div);
  }
  createOption("", "None");
  for (var checkbox of settings.checkboxes) {
    createOption(checkbox, checkbox);
  }
}

function updateCheckboxStats(checkbox) {
  var checked = getStoredCheckboxRefs(checkbox).size;
  var total = pcbdata.footprints.length - pcbdata.bom.skipped.length;
  var percent = checked * 100.0 / total;
  var td = document.getElementById("checkbox-stats-" + checkbox);
  td.firstChild.style.width = percent + "%";
  td.lastChild.innerHTML = checked + "/" + total + " (" + Math.round(percent) + "%)";
}

document.onkeydown = function(e) {
  switch (e.key) {
    case "n":
      if (document.activeElement.type == "text") {
        return;
      }
      if (currentHighlightedRowId !== null) {
        checkBomCheckbox(currentHighlightedRowId, "placed");
        highlightNextRow();
        e.preventDefault();
      }
      break;
    case "ArrowUp":
      highlightPreviousRow();
      e.preventDefault();
      break;
    case "ArrowDown":
      highlightNextRow();
      e.preventDefault();
      break;
    default:
      break;
  }
  if (e.altKey) {
    switch (e.key) {
      case "f":
        focusFilterField();
        e.preventDefault();
        break;
      case "r":
        focusRefLookupField();
        e.preventDefault();
        break;
      case "z":
        changeBomLayout("bom-only");
        e.preventDefault();
        break;
      case "x":
        changeBomLayout("left-right");
        e.preventDefault();
        break;
      case "c":
        changeBomLayout("top-bottom");
        e.preventDefault();
        break;
      case "v":
        changeCanvasLayout("F");
        e.preventDefault();
        break;
      case "b":
        changeCanvasLayout("FB");
        e.preventDefault();
        break;
      case "n":
        changeCanvasLayout("B");
        e.preventDefault();
        break;
      default:
        break;
    }
    if (e.key >= '1' && e.key <= '9') {
      toggleBomCheckbox(currentHighlightedRowId, parseInt(e.key));
    }
  }
}

function hideNetlistButton() {
  document.getElementById("bom-ungrouped-btn").classList.remove("middle-button");
  document.getElementById("bom-ungrouped-btn").classList.add("right-most-button");
  document.getElementById("bom-netlist-btn").style.display = "none";
}

window.onload = function(e) {
  initUtils();
  initRender();
  initStorage();
  initDefaults();
  cleanGutters();
  populateMetadata();
  dbgdiv = document.getElementById("dbg");
  bom = document.getElementById("bombody");
  bomhead = document.getElementById("bomhead");
  filter = "";
  reflookup = "";
  if (!("nets" in pcbdata)) {
    hideNetlistButton();
  }
  initDone = true;
  prepCheckboxes();
  // Triggers render
  changeBomLayout(settings.bomlayout);

  // Users may leave fullscreen without touching the checkbox. Uncheck.
  document.addEventListener('fullscreenchange', () => {
    if (!document.fullscreenElement)
      document.getElementById('fullscreenCheckbox').checked = false;
  });
}

window.onresize = resizeAll;
window.matchMedia("print").addListener(resizeAll);

///////////////////////////////////////////////

///////////////////////////////////////////////
 
///////////////////////////////////////////////
  </script>
</head>

<body>
<!-- USERHEADER -->
<div id="topmostdiv" class="topmostdiv">
  <div id="top">
    <div style="float: right; height: 100%;">
      <div class="hideonprint menu" style="float: right; top: 8px;">
        <button class="menubtn"></button>
        <div class="menu-content">
          <label class="menu-label menu-label-top" style="width: calc(50% - 18px)">
            <input id="darkmodeCheckbox" type="checkbox" onchange="setDarkMode(this.checked)">
            Dark mode
          </label><!-- This comment eats space! All of it!
          --><label class="menu-label menu-label-top" style="width: calc(50% - 17px); border-left: 0;">
            <input id="fullscreenCheckbox" type="checkbox" onchange="setFullscreen(this.checked)">
            Full Screen
          </label>
          <label class="menu-label" style="width: calc(50% - 18px)">
            <input id="fabricationCheckbox" type="checkbox" checked onchange="fabricationVisible(this.checked)">
            Fab layer
          </label><!-- This comment eats space! All of it!
          --><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
            <input id="silkscreenCheckbox" type="checkbox" checked onchange="silkscreenVisible(this.checked)">
            Silkscreen
          </label>
          <label class="menu-label" style="width: calc(50% - 18px)">
            <input id="referencesCheckbox" type="checkbox" checked onchange="referencesVisible(this.checked)">
            References
          </label><!-- This comment eats space! All of it!
          --><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
            <input id="valuesCheckbox" type="checkbox" checked onchange="valuesVisible(this.checked)">
            Values
          </label>
          <div id="tracksAndZonesCheckboxes">
            <label class="menu-label" style="width: calc(50% - 18px)">
              <input id="tracksCheckbox" type="checkbox" checked onchange="tracksVisible(this.checked)">
              Tracks
            </label><!-- This comment eats space! All of it!
            --><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
              <input id="zonesCheckbox" type="checkbox" checked onchange="zonesVisible(this.checked)">
              Zones
            </label>
          </div>
          <label class="menu-label" style="width: calc(50% - 18px)">
            <input id="padsCheckbox" type="checkbox" checked onchange="padsVisible(this.checked)">
            Pads
          </label><!-- This comment eats space! All of it!
          --><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
            <input id="dnpOutlineCheckbox" type="checkbox" checked onchange="dnpOutline(this.checked)">
            DNP outlined
          </label>
          <label class="menu-label">
            <input id="highlightpin1Checkbox" type="checkbox" onchange="setHighlightPin1(this.checked)">
            Highlight first pin
          </label>
          <label class="menu-label">
            <input id="dragCheckbox" type="checkbox" checked onchange="setRedrawOnDrag(this.checked)">
            Continuous redraw on drag
          </label>
          <label class="menu-label">
            <input id="crosshairCheckbox" type="checkbox" checked onchange="setShowCrosshair(this.checked)">
            Show Crosshair
          </label>
          <label class="menu-label">
            <span>Board rotation</span>
            <span style="float: right"><span id="rotationDegree">0</span>&#176;</span>
            <input id="boardRotation" type="range" min="-36" max="36" value="0" class="slider" oninput="setBoardRotation(this.value)">
          </label>
          <label class="menu-label">
            <div style="margin-left: 5px">Bom checkboxes</div>
            <input id="bomCheckboxes" class="menu-textbox" type=text
                   oninput="setBomCheckboxes(this.value)">
          </label>
          <label class="menu-label">
            <div style="margin-left: 5px">Darken when checked</div>
            <div id="darkenWhenCheckedContainer"></div>
          </label>
          <label class="menu-label">
            <span class="shameless-plug">
              <span>Created using</span>
              <a id="github-link" target="blank" href="https://github.com/openscopeproject/InteractiveHtmlBom">InteractiveHtmlBom</a>
            </span>
          </label>
        </div>
      </div>
      <div class="button-container hideonprint"
           style="float: right; position: relative; top: 8px">
        <button id="fl-btn" class="left-most-button" onclick="changeCanvasLayout('F')"
                title="Front only">F
        </button>
        <button id="fb-btn" class="middle-button" onclick="changeCanvasLayout('FB')"
                title="Front and Back">FB
        </button>
        <button id="bl-btn" class="right-most-button" onclick="changeCanvasLayout('B')"
                title="Back only">B
        </button>
      </div>
      <div class="button-container hideonprint"
           style="float: right; position: relative; top: 8px">
        <button id="bom-btn" class="left-most-button" onclick="changeBomLayout('bom-only')"
                title="BOM only"></button>
        <button id="lr-btn" class="middle-button" onclick="changeBomLayout('left-right')"
                title="BOM left, drawings right"></button>
        <button id="tb-btn" class="right-most-button" onclick="changeBomLayout('top-bottom')"
                title="BOM top, drawings bot"></button>
      </div>
      <div class="button-container hideonprint"
           style="float: right; position: relative; top: 8px">
        <button id="bom-grouped-btn" class="left-most-button" onclick="changeBomMode('grouped')"
                title="Grouped BOM"></button>
        <button id="bom-ungrouped-btn" class="middle-button" onclick="changeBomMode('ungrouped')"
                title="Ungrouped BOM"></button>
        <button id="bom-netlist-btn" class="right-most-button" onclick="changeBomMode('netlist')"
                title="Netlist"></button>
      </div>
      <div class="hideonprint menu" style="float: right; top: 8px;">
        <button class="statsbtn"></button>
        <div class="menu-content">
          <table class="stats">
            <tbody>
              <tr>
                <td width="40%">Board stats</td>
                <td>Front</td>
                <td>Back</td>
                <td>Total</td>
              </tr>
              <tr>
                <td>Components</td>
                <td id="stats-components-front">~</td>
                <td id="stats-components-back">~</td>
                <td id="stats-components-total">~</td>
              </tr>
              <tr>
                <td>Groups</td>
                <td id="stats-groups-front">~</td>
                <td id="stats-groups-back">~</td>
                <td id="stats-groups-total">~</td>
              </tr>
              <tr>
                <td>SMD pads</td>
                <td id="stats-smd-pads-front">~</td>
                <td id="stats-smd-pads-back">~</td>
                <td id="stats-smd-pads-total">~</td>
              </tr>
              <tr>
                <td>TH pads</td>
                <td colspan=3 id="stats-th-pads">~</td>
              </tr>
            </tbody>
          </table>
          <table class="stats">
            <col width="40%"/><col />
            <tbody id="checkbox-stats">
              <tr>
                <td colspan=2 style="border-top: 0">Checkboxes</td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
      <div class="hideonprint menu" style="float: right; top: 8px;">
        <button class="iobtn"></button>
        <div class="menu-content">
          <div class="menu-label menu-label-top">
            <div style="margin-left: 5px;">Save board image</div>
            <div class="flexbox">
              <input id="render-save-width" class="menu-textbox" type="text" value="1000" placeholder="Width"
                  style="flex-grow: 1; width: 50px;" oninput="validateSaveImgDimension(this)">
              <span>X</span>
              <input id="render-save-height" class="menu-textbox" type="text" value="1000" placeholder="Height"
                  style="flex-grow: 1; width: 50px;" oninput="validateSaveImgDimension(this)">
            </div>
            <label>
              <input id="render-save-transparent" type="checkbox">
              Transparent background
            </label>
            <div class="flexbox">
              <button class="savebtn" onclick="saveImage('F')">Front</button>
              <button class="savebtn" onclick="saveImage('B')">Back</button>
            </div>
          </div>
          <div class="menu-label">
            <span style="margin-left: 5px;">Config and checkbox state</span>
            <div class="flexbox">
              <button class="savebtn" onclick="saveSettings()">Export</button>
              <button class="savebtn" onclick="loadSettings()">Import</button>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div id="fileinfodiv" style="overflow: auto;">
      <table class="fileinfo">
        <tbody>
          <tr>
            <td id="title" class="title" style="width: 70%">
              Title
            </td>
            <td id="revision" class="title" style="width: 30%">
              Revision
            </td>
          </tr>
          <tr>
            <td id="company">
              Company
            </td>
            <td id="filedate">
              Date
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
  <div id="bot" class="split" style="height: calc(100% - 80px)">
    <div id="bomdiv" class="split split-horizontal">
      <div style="width: 100%">
        <input id="reflookup" class="textbox searchbox reflookup hideonprint" type="text" placeholder="Ref lookup"
               oninput="updateRefLookup(this.value)">
        <input id="filter" class="textbox searchbox filter hideonprint" type="text" placeholder="Filter"
               oninput="updateFilter(this.value)">
        <div class="button-container hideonprint" style="float: left; margin: 0;">
          <button id="copy" title="Copy bom table to clipboard"
               onclick="copyToClipboard()"></button>
        </div>
      </div>
      <div id="dbg"></div>
      <table class="bom">
        <thead id="bomhead">
        </thead>
        <tbody id="bombody">
        </tbody>
      </table>
    </div>
    <div id="canvasdiv" class="split split-horizontal">
      <div id="frontcanvas" class="split" touch-action="none" style="overflow: hidden">
        <div style="position: relative; width: 100%; height: 100%;">
          <canvas id="F_bg" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
          <canvas id="F_fab" style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
          <canvas id="F_slk" style="position: absolute; left: 0; top: 0; z-index: 2;"></canvas>
          <canvas id="F_hl" style="position: absolute; left: 0; top: 0; z-index: 3;"></canvas>
        </div>
      </div>
      <div id="backcanvas" class="split" touch-action="none" style="overflow: hidden">
        <div style="position: relative; width: 100%; height: 100%;">
          <canvas id="B_bg" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
          <canvas id="B_fab" style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
          <canvas id="B_slk" style="position: absolute; left: 0; top: 0; z-index: 2;"></canvas>
          <canvas id="B_hl" style="position: absolute; left: 0; top: 0; z-index: 3;"></canvas>
        </div>
      </div>
    </div>
  </div>
</div>
<!-- USERFOOTER -->
</body>

</html>
